def __init__(self, chat_manager, message, interceptors=None): from botshot.core.logging.logging_service import AsyncLoggingService flows = get_flows() if flows is None: raise ValueError("Flows have not been initialized.") if chat_manager is None: raise ValueError("ChatManager is None") if message is None: raise ValueError("Message is None") if message.conversation is None: raise ValueError("Conversation is None") self.message = message self.chat_manager = chat_manager self.send_exceptions = config.get("SEND_EXCEPTIONS", default=settings.DEBUG) self.flows = flows self.current_state_name = self.message.conversation.state or 'default.root' self.context = Context.from_dict(dialog=self, data=message.conversation.context_dict or {}) loggers = [ import_string(path)() for path in config.get('MESSAGE_LOGGERS', default=[]) ] self.logging_service = AsyncLoggingService(loggers) self.dialog = Dialog(message=self.message, context=self.context, chat_manager=self.chat_manager, logging_service=self.logging_service) self.interceptors = interceptors or []
def __init__(self): super().__init__() self.base_url = 'https://chatbase.com/api' self.api_key = settings.BOT_CONFIG.get("CHATBASE_API_KEY") self.bot_version = config.get("VERSION", 'no version') if self.api_key is None: logging.warning("Chatbase API key not provided, will not log!")
def __init__(self): super().__init__() self.verify_token = config.get_required('FB_VERIFY_TOKEN') self.pages = self._init_pages() self.adapter = FacebookAdapter(self) self.profile_fields = config.get('FB_PROFILE_FIELDS', 'first_name,last_name,profile_pic')
def __init__(self): from botshot.core.interceptors import AdminDialogInterceptor, BotshotVersionDialogInterceptor self.save_messages = config.get("SAVE_MESSAGES", True) # TODO: Register extra interceptors in config self.interceptors = [ AdminDialogInterceptor(), BotshotVersionDialogInterceptor() ]
def get_flows(cache=True): """Creates flows from their YAML definitions.""" global _FLOWS if not cache or _FLOWS is None: import yaml flows = {} # a dict with all the flows loaded from YAML BOTS = config.get('BOTS', []) for filename in BOTS: try: with open(os.path.join(settings.BASE_DIR, filename)) as f: definitions = yaml.load(f) if not definitions: logging.warning( "Skipping empty flow definition {}".format( filename)) break for flow_name in definitions: if flow_name in flows: raise Exception( "Error: duplicate flow {}".format(flow_name)) definition = definitions[flow_name] flow = Flow.load(flow_name, definition, relpath=os.path.dirname(filename)) flows[flow_name] = flow except OSError as e: raise ValueError( "Unable to open definition {}".format(filename)) from e except TypeError as e: raise ValueError( "Unable to read definition {}".format(filename)) from e if not flows.get('default') or not flows.get('default').get_state( 'root'): raise Exception( "Required state default.root was not found. " "Please add this state, Botshot uses it as the first state when starting a conversation." ) print('Initialized {} flows: {}'.format(len(flows), sorted(list(flows.keys())))) _FLOWS = flows return _FLOWS
def get_redis(): """ Returns an instance of Redis cache, or None if Redis is not configured. """ global _connection_pool global _redis if not _connection_pool: redis_url = config.get('REDIS_URL') if not redis_url: warnings.warn( "Redis not available, returning None. Set REDIS_URL in BOT_CONFIG to enable cache." ) return None redis_url_parsed = urlparse(redis_url) _connection_pool = redis.ConnectionPool( host=redis_url_parsed.hostname, port=redis_url_parsed.port, password=redis_url_parsed.password, db=0, max_connections=2) if not _redis: _redis = redis.StrictRedis(connection_pool=_connection_pool) return _redis
class TelegramInterface(BasicAsyncInterface): name = 'telegram' adapter = TelegramAdapter() base_url = 'https://api.telegram.org/bot{}/'.format( config.get_required('TELEGRAM_TOKEN')) should_hide_keyboard = config.get('TELEGRAM_HIDE_KEYBOARD', False) def on_server_startup(self): from django.urls import reverse endpoint_url = self.base_url + 'setWebhook' deploy_url = config.get_required( 'DEPLOY_URL', "Telegram requires BOT_CONFIG.DEPLOY_URL to be set in order to register webhooks." ) deploy_url = deploy_url[:-1] if deploy_url[-1] == '/' else deploy_url callback_url = deploy_url + reverse('interface_webhook', args=['telegram']) logging.info('Reverse telegram URL is: {}'.format(callback_url)) payload = {'url': callback_url} response = requests.post(endpoint_url, data=payload) if not response.json()['ok']: raise Exception( "Error while registering telegram webhook: {}".format( response.json())) def parse_raw_messages(self, request) -> Generator[RawMessage, None, None]: update_id = request[ 'update_id'] # TODO: use this counter for synchronization if 'message' in request: yield self._parse_raw_message(request['message']) elif 'channel_post' in request: yield self._parse_raw_message(request['channel_post']) elif 'callback_query' in request: # payload button clicked yield self._parse_callback_query(request['callback_query']) else: logging.warning("Ignoring unsupported telegram request") def _parse_raw_message(self, message): chat_id = message['chat']['id'] user_id = message.get('from', {}).get( 'id') # empty for messages sent to channels meta = {} date = message['date'] text = None if 'text' in message: text = message['text'] # UTF-8, 0-4096 characters else: logging.warning("Ignoring unsupported telegram message") return None return RawMessage(interface=self, raw_user_id=user_id, raw_conversation_id=chat_id, conversation_meta=meta, type=ChatMessage.MESSAGE, text=text, payload=None, timestamp=date) def _parse_callback_query(self, callback): query_id = callback['id'] user_id = callback['from']['id'] chat_instance_id = callback['chat_instance'] # this isn't chat_id message = callback.get('message') data_key = callback.get('data') # TODO: sanitize if not message: logging.warning( "Ignoring telegram callback without original message") return None if not data_key: logging.warning("Ignoring telegram callback without data") return None chat_id = message['chat']['id'] message_id = message['message_id'] data = self._retrieve_callback(data_key) data = json.loads(data) # answer query to hide loading bar in frontend url = self.base_url + 'answerCallbackQuery' payload = {'callback_query_id': query_id} response = requests.post(url, data=payload) if not response.json()['ok']: logging.error( 'Error occurred while answering to telegram callback: {}'. format(response.json())) # hide keyboard with buttons (not quick replies) if self.should_hide_keyboard: url = self.base_url + 'editMessageReplyMarkup' payload = { 'chat_id': chat_id, 'message_id': message_id, 'reply_markup': '' } response = requests.post(url, data=payload) if not response.json()['ok']: logging.error( 'Error occurred while hiding telegram keyboard: {}'.format( response.json())) return RawMessage(interface=self, raw_user_id=user_id, raw_conversation_id=chat_id, conversation_meta={}, type=ChatMessage.BUTTON, text=None, payload=data, timestamp=time()) @classmethod def _persist_callback(cls, payload) -> str: """ Persists payload to be used with Telegram callbacks (which can be max. 64B) and returns a string that can be used to retrieve the payload from redis. The payload will be persisted for a week. :param payload Payload to be persisted in Redis. :return Unique string associated with the payload, which can be sent to Telegram. """ redis = get_redis() key = ''.join(random.choice(string.hexdigits) for i in range(63)) while redis.exists(key): key = ''.join(random.choice(string.hexdigits) for i in range(63)) redis.set(key, payload, ex=3600 * 24 * 7) return key @classmethod def _retrieve_callback(cls, key) -> Optional[str]: redis = get_redis() if redis.exists(key): return redis.get(key).decode('utf8') return None def on_message_processing_start(self, message: ChatMessage): url = self.base_url + 'sendChatAction' payload = { 'chat_id': message.conversation.raw_conversation_id, 'action': 'typing' } response = requests.post(url, data=payload) if not response.json()['ok']: logging.warning( "Error occurred while sending Telegram typing action: {}". format(response.json())) def send_responses(self, conversation, reply_to, responses): messages = [] chat_id = conversation.raw_conversation_id for response in responses: messages += self.adapter.transform_message(response, chat_id) for method, payload in messages: url = self.base_url + method response = requests.post(url, data=payload) if not response.json()['ok']: logging.warning( "Telegram message for method {} failed to send: {}".format( url, response.json())) break def broadcast_responses(self, conversations, responses): for conversation in conversations: self.send_responses(conversation=conversation, reply_to=None, responses=responses) ######################################################################################################################## @staticmethod def has_message_expired(message: dict) -> bool: if not (message and 'date' in message): logging.warning('Invalid date in message') return True received = datetime.fromtimestamp(int(message['date'])) now = datetime.utcnow() if abs(now - received) > timedelta( seconds=settings.BOT_CONFIG['MSG_LIMIT_SECONDS']): logging.warning('Ignoring message, too old') return True return False
def __init__(self): self.msg_limit_seconds = config.get('MSG_LIMIT_SECONDS', 15)
def __init__(self): super().__init__() self.base_url = 'https://chatbase.com/api' self.api_key = config.get_required("CHATBASE_API_KEY", "Chatbase API key not provided!") self.bot_version = config.get("VERSION", 'no version')