def init(self, core): self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('channel_telegram'), self, self.core, 'ChannelTelegram') self.logger.debug("Listening Telegram from path: " + self.get_webhook_path())
def init(self, core: BBotCore): """ Initializes bot """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('rasaserver_cbe'), self, self.core)
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('dotrepository'), self, self.core, 'dotrepository') uri = self.config['uri'] uri_parts = pymongo.uri_parser.parse_uri(uri) database_name = uri_parts['database'] self.logger.debug("Initializing mongodb with URI " + uri.replace(str(uri_parts['password']), '********')) if uri in DotRepository.mongo_clients.keys(): # look in cache self.logger.debug("Found mongo client already active in memory") self.mongo = DotRepository.mongo_clients[uri] return client = MongoClient(uri, serverSelectionTimeoutMS=self.connection_timeout) self.mongo = client[database_name] DotRepository.mongo_clients[uri] = client[database_name]
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('pipeline.templator'), self, self.core, 'Templetor')
def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('chatscript_cbe'), self, self.core)
def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.url = 'https://api.pandorabots.com' self.logger = BBotLoggerAdapter(logging.getLogger('pandora_cbe'), self, self.core)
def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.visemes = None self.logger = BBotLoggerAdapter(logging.getLogger('actr'), self, self, 'actr')
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter( logging.getLogger('pipeline.fallbackbots'), self, self.core, 'FallbackBots')
class ACTR(): def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.visemes = None self.logger = BBotLoggerAdapter(logging.getLogger('actr'), self, self, 'actr') def init(self, core): """Initializes some values""" pass def get_actr(self, text: str, locale: str, voice_id: str, time_scale: int): """ """ # get visemes self.visemes.voice_locale = locale self.visemes.voice_id = voice_id visemes = self.visemes.get_visemes(text, time_scale) # fixes amazon polly response # adds a start delay and provides duration for each viseme first = True new_visemes = [] start_delay = 0 for idx, v in enumerate(visemes): # if this is the first viseme then time is start_delay value if first: start_delay = v['time'] first = False if idx == len(visemes) - 1: # this is the last viseme. let duration as the median of previous viseme duration # @TODO duration = 100 else: # get duration by the substraction of next viseme time and current time next_t = visemes[idx + 1]['time'] duration = next_t - v['time'] new_visemes.append({'value': v['value'], 'duration': duration}) new_visemes.insert(0,{'value': 'sil', 'duration': start_delay}) response = { 'visemes': new_visemes } self.logger.debug('Visemes:' + str(response)) return response
def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.parity_node_rpc_url = '' self.logger = BBotLoggerAdapter(logging.getLogger('token_parityeth'), self, self, '')
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('ext.token_mgnt'), self, self.core.bot, '$token') smokesignal.on(BBotCore.SIGNAL_CALL_BBOT_FUNCTION_BEFORE, self.function_payment) smokesignal.on(BBotCore.SIGNAL_GET_RESPONSE_AFTER, self.volley_payment) smokesignal.on(BBotCore.SIGNAL_GET_RESPONSE_BEFORE, self.payment_check)
def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.seed_wallet_api_key = '' self.seed_wallet_url = '' self.logger = BBotLoggerAdapter(logging.getLogger('token_seedwallet'), self, self, '')
def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. """ self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.logger = None self.logger = BBotLoggerAdapter(logging.getLogger('core_ext.reg_act'), self, self.core, 'RegisterActivity')
def init(self, core: BBotCore): """ Initializes python bot """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('python_cbe'), self, self.core)
def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.voice_id = 1 self.voice_locale = 'en_US' self.tts_service_name = 'AmazonPolly' # https://docs.aws.amazon.com/polly/latest/dg/voices-in-polly.html self.voice_id_locale_map = { 'en_US': ['Joanna', 'Ivy', 'Kendra', 'Kimberly', 'Salli', 'Joey', 'Justin', 'Matthew'], 'en_GB': ['Emma', 'Amy', 'Brian'] } self.logger = BBotLoggerAdapter(logging.getLogger('tts'), self, self, 'tts')
def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('dialogfl_cbe'), self, self.core) self.chatbot_engine = self.dotbot.chatbot_engine self.service_account = json.loads( self.chatbot_engine['serviceAccount']) credentials = Credentials.from_service_account_info( self.service_account) self.session_client = dialogflow.SessionsClient( credentials=credentials)
def init(self, core: ChatbotEngine): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter( logging.getLogger('core_ext.send_email'), self, self.core, '$sendEmail') core.register_function( 'sendEmail', { 'object': self, 'method': 'sendEmail', 'cost': 0.001, 'register_enabled': True })
def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('watson_cbe'), self, self.core) # Set up Assistant service. authenticator = IAMAuthenticator( self.dotbot.chatbot_engine['iamApikey']) self.service = ibm_watson.AssistantV2(authenticator=authenticator, version='2019-02-28') self.service.set_service_url(self.dotbot.chatbot_engine['url']) self.logger.debug("Connection: " + str(self.service))
class WolframAlphaShortAnswers(ChatbotEngine): """ """ def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. :param config: Configuration values for the instance. """ super().__init__(config, dotbot) def init(self, core: BBotCore): """ Initializes bot """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('wolframsa_cbe'), self, self.core) def get_response(self, request: dict) -> dict: """ Return a response based on the input data. :param request: A dictionary with input data. :return: A response to the input data. """ super().get_response(request) appid = self.dotbot.chatbot_engine['appId'] query = self.request['input']['text'] self.logger.debug( 'Querying to Wolfram Alpha Short Answers API with query: ' + query) r = requests.get( f'http://api.wolframalpha.com/v1/result?appid={appid}&i={query}') self.logger.debug('Wolfram Alpha Short Answers API response code: ' + str(r.status_code) + ' - message: ' + str(r.text)[0:300]) if r.status_code == 200: aw = r.text if r.status_code == 501: aw = r.text # Because this bot is designed to return a single result, this message may appear if no sufficiently short result can be found. You may occasionally receive this answer when requesting information on topics that are restricted or not covered. self.core.bbot.text(aw) return self.response
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('ext.r_services'), self, self.core.bot, '$call_api') # get services for the bot services = self.get_bot_services() for serv in services: self.logger.debug('Register service ' + serv['name']) self.logger.debug(str(serv)) fmap = { 'owner_name': serv['ownerName'], 'cost': serv['cost'], 'subscription_type': serv['subscriptionType'], 'subscription_id': serv['subscriptionId'], 'register_enabled': True } if serv['url'] is not '': fmap = { **fmap, **{ 'object': self, 'method': serv['function_name'], 'url': serv['url'], 'request_method': serv['method'], 'timeout': serv['timeout'], 'predefined_vars': serv['predefined_vars'], 'headers': serv['headers'], 'user': serv.get('user'), 'passwd': serv.get('passwd'), 'mapped_vars': serv['mapped_vars'], } } core.register_function(serv['function_name'], fmap)
def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('dialogfl_cbe'), self, self.core) self.service_account = json.loads(self.dotbot.chatbot_engine['serviceAccount']) self.platform = None self.available_platforms = { 'google_assistant': 'ACTIONS_ON_GOOGLE', 'facebook': 'FACEBOOK', 'slack': 'SLACK', 'telegram': 'TELEGRAM', 'skype': 'SKYPE' } credentials = Credentials.from_service_account_info(self.service_account) self.session_client = dialogflow.SessionsClient(credentials=credentials)
def init(self, core: BBotCore): """ Initializes extension :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('core.ext.response'), self, self.core.bot, 'bbotoutput') for f in self.functions: core.register_function(f, {'object': self, 'method': f, 'cost': 0, 'register_enabled': False})
def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('core_fnc.weather'), self, self.core.bot, '$weather') self.method_name = 'weather' self.accuweather_text = 'Weather forecast provided by Accuweather' self.accuweather_image_url = 'https://static.seedtoken.io/AW_RGB.png' core.register_function( 'weather', { 'object': self, 'method': self.method_name, 'cost': 0.1, 'register_enabled': True }) # we register this to add accuweather text even when result is cached from extensions_cache decorator smokesignal.on(BBotCore.SIGNAL_CALL_BBOT_FUNCTION_AFTER, self.add_accuweather_text)
class TokenManagerParityETH(): web3 = None def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.parity_node_rpc_url = '' self.logger = BBotLoggerAdapter(logging.getLogger('token_parityeth'), self, self, '') def init(self, core): """Initializes some values""" pass def connect(self): if not TokenManagerParityETH.web3: self.logger.debug('Connection to node ' + self.parity_node_rpc_url) if 'http' in self.parity_node_rpc_url: provider = Web3.HTTPProvider(self.parity_node_rpc_url) if 'ws://' in self.parity_node_rpc_url: provider = Web3.WebsocketProvider(self.parity_node_rpc_url) if 'file://' in self.parity_node_rpc_url: url = self.parity_node_rpc_url.replace('file://', '') #web3 does not recognize file:// scheme provider = Web3.IPCProvider(url) TokenManagerParityETH.web3 = Web3(provider) def transfer(self, fromAddress: str, toAddress: str, amount: float, credential: str=''): self.connect() self.logger.debug('Transfering ' + str(amount) + 'ETH from ' + fromAddress + ' to ' + toAddress) response = TokenManagerParityETH.web3.parity.personal.sendTransaction( { 'to': Web3.toChecksumAddress(toAddress), 'from': Web3.toChecksumAddress(fromAddress), 'value': Web3.toWei(amount, 'ether'), 'gas': 21000, 'gasPrice': 0 }, credential) self.logger.debug('Transaction hash: ' + response.hex()) return response
class DotRepository(): """MongoDB client.""" mongo_clients = {} # mongo clients cache def __init__(self, config: dict, dotbot: dict = None) -> None: """Initialize the connection.""" self.config = config self.logger_level = '' self.connection_timeout = 5000 if 'uri' not in config: raise RuntimeError("FATAL ERR: Missing config var uri") def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('dotrepository'), self, self.core, 'dotrepository') uri = self.config['uri'] uri_parts = pymongo.uri_parser.parse_uri(uri) database_name = uri_parts['database'] self.logger.debug("Initializing mongodb with URI " + uri.replace(str(uri_parts['password']), '********')) if uri in DotRepository.mongo_clients.keys(): # look in cache self.logger.debug("Found mongo client already active in memory") self.mongo = DotRepository.mongo_clients[uri] return client = MongoClient(uri, serverSelectionTimeoutMS=self.connection_timeout) self.mongo = client[database_name] DotRepository.mongo_clients[uri] = client[database_name] def restart_from_scratch(self): """Drop and recreate each collection in database.""" collections = ['organizations', 'users', 'dotbot', 'dotflow'] collections_in_db = self.mongo.list_collection_names() for collection_name in collections: if collection_name in collections_in_db: self.mongo.drop_collection(collection_name) self.mongo.create_collection(collection_name) # Unique organization name self.mongo.organizations.create_index('name', unique=True) # Unique username and token self.mongo.users.create_index('username', unique=True) self.mongo.users.create_index('token', unique=True) # Unique dotbot name #self.mongo.dotbot.create_index('dotbot.name', unique=True) # Unique dotflow name by dotbot #self.mongo.dotflow.create_index([('name', ASCENDING), # ('dotbot_id', ASCENDING)], # unique=True) @staticmethod def get_projection_from_fields(fields: list = []): """Returns a mongodb projection based on a list of fieldnames in dot notation (note _id will be included always)""" return dict([field, 1] for field in fields if len(field) > 0) if len(fields[0]) else None ### ORGANIZATIONS (deprecated?) def create_organization(self, name: str) -> Organization: """ Create a new organization. :param name: Unique organization name. :return: The organization created. """ params = {'name': name} organization_id = self.mongo.organizations.insert_one( params).inserted_id result = self.mongo.organizations.find_one( {"_id": ObjectId(str(organization_id))}) organization = Organization() organization.id = str(organization_id) organization.name = result['name'] return organization def find_one_organization(self, filters: dict) -> Organization: """ Retrieve an organization by filters. :param filters: Dictionary with matching conditions. :return: Organization instance or None if not found. """ result = self.mongo.organizations.find_one(filters) if not result: return None organization = Organization() organization.id = str(result['_id']) organization.name = result['name'] return organization ### USERS/AUTH (deprecated?) def find_one_user(self, filters: dict) -> User: """ Retrieve a user by filters. :param filters: Dictionary with matching conditions. :return: User instance or None if not found. """ result = self.mongo.users.find_one(filters) if not result: return None user = User() user.id = str(result['_id']) user.username = result['username'] user.hashed_password = result['hashed_password'] organization_id = ObjectId(str(result['organization_id'])) user.organization = self.find_one_organization( {'_id': organization_id}) return user def create_user(self, username: str, plaintext_password: str, organization: Organization, is_admin: int) -> User: """ Create a new user. :param username: Unique username. :param plaintext_password: Password in plain text that won't be stored. :param organization: A valid organization. :param is_admin: If the user has administrative privileges: 1. Default: 0. :return: User created. """ param = { 'username': username, 'hashed_password': bcrypt.hashpw( plaintext_password, # pylint: disable=no-member bcrypt.gensalt()), 'organization_id': organization.id, 'is_admin': is_admin, 'token': '', 'created_at': datetime.datetime.utcnow(), 'updated_at': datetime.datetime.utcnow() } user_id = self.mongo.users.insert_one(param).inserted_id return self.find_one_user({"_id": ObjectId(str(user_id))}) def login(self, username: str, plaintext_password: str) -> Token: """ Try to authenticate a user. :param username: Unique username. :param plaintext_password: Password. :raise AuthenticationError: on failure. :return: Authentication token. """ token = Token() user = self.find_one_user({"username": username}) if user: if bcrypt.checkpw( plaintext_password, # pylint: disable=no-member user.hashed_password): token.status = 'success' token.token = os.urandom(24).hex() self.mongo.users.update_one({"username": username}, { "$set": { "token": token.token, 'updated_at': datetime.datetime.utcnow() } }) if token.status != 'success': raise AuthenticationError() return token def logout(self, username: str) -> None: """ Remove the authentication token from a user. :param username: A valid username :return: None """ self.mongo.users.update_one({"username": username}, {"$set": {"token": "", "updated_at": \ datetime.datetime.utcnow()}}) def find_user_by_token(self, token: str) -> User: """ Retrieve a user by its authentication token. :param token: A valid authentication token. :return: User related to token. """ return self.find_one_user({"token": token}) ### DOTBOTCONTAINER def marshall_dotbot_container(self, result) -> DotBotContainer: """ Marshall a dotbotcontainer. :param result: A mongodb document representing a dotbot. :return: DotBotContainer instance """ dotbot_container = DotBotContainer() dotbot_container.dotbot = result['dotbot'] dotbot_container.organization = self.find_one_organization( {'_id': ObjectId(str(result['organizationId']))}) dotbot_container.deleted = result['deleted'] dotbot_container.createdAt = result['createdAt'] dotbot_container.updatedAt = result['updatedAt'] return dotbot_container def find_dotbot_containers(self, filters: dict) -> list: """ Retrieve a list of dotbots. :param filters: Dictionary with matching conditions. :return: List of dotbots """ results = self.mongo.dotbot.find(filters) dotbots = [] for result in results: dotbots.append(self.marshall_dotbot_container(result)) return dotbots def find_one_dotbot_container(self, filters: dict) -> DotBotContainer: """ Retrieve a dotbotContainer by filters. :param filters: Dictionary with matching conditions. :return: DotBotContainer instance or None if not found. """ result = self.mongo.dotbot.find_one(filters) if not result: return None return self.marshall_dotbot_container(result) def find_dotbot_container_by_container_id( self, container_id: str) -> DotBotContainer: """ Retrieve a dotbot by its container ID. :param container_id: DotBot container ID :return: DotBotContainer instance or None if not found. """ return self.find_one_dotbot_container( {"_id": ObjectId(str(container_id))}) def find_dotbot_container_by_idname(self, dotbot_idname: str) -> DotBotContainer: """ Retrieve a dotbot by its id or name. :param dotbot_idname: DotBot id or name :return: DotBot instance or None if not found. """ return self.find_one_dotbot_container({ '$or': [{ 'dotbot.id': dotbot_idname }, { 'dotbot.name': dotbot_idname }] }) def find_dotbots_by_channel(self, channel: str) -> list: """ Retrieve a dotbot list by its enabled channels :param channel: DotBot enabled channel :return: DotBot intance list """ return self.find_dotbot_containers( {'dotbot.channels.' + channel + '.enabled': True}) def create_dotbot(self, dotbot: dict, organization: Organization) -> DotBotContainer: """ Create a new DotBot. :param dotbot: DotBot data :param organization: A valid organization. :return: DotBot created. """ oid = ObjectId() if not dotbot.get('id'): # Insert oid on dotbot id if not set dotbot['id'] = str(oid) param = { '_id': oid, 'dotbot': dotbot, 'organizationId': organization.id, 'deleted': '0', 'createdAt': datetime.datetime.utcnow(), 'updatedAt': datetime.datetime.utcnow() } self.mongo.dotbot.insert_one(param) return self.find_dotbot_container_by_container_id(oid) def update_dotbot_by_container_id(self, container_id: str, dotbot: dict) -> DotBotContainer: """ Update a DotBot by its container id. :param container_id: DotBot container id :param dotbot: DotBotContainer object :return: Updated DotBot """ self.mongo.dotbot.update_one({"_id": ObjectId(str(container_id))}, { "$set": { "dotbot": dotbot, "updatedAt": datetime.datetime.utcnow() } }) return self.find_dotbot_container_by_container_id(container_id) def update_dotbot_by_idname(self, dotbot_idname: str, dotbot: dict) -> DotBotContainer: """ Update a DotBot by its id. :param dotbot_id: DotBot id :param dotbot: DotBot object :return: Updated DotBotContainer object """ self.mongo.dotbot.update_one( { '$or': [{ 'dotbot.id': dotbot_idname }, { 'dotbot.name': dotbot_idname }] }, { "$set": { "dotbot": dotbot, "updatedAt": datetime.datetime.utcnow() } }) return self.find_dotbot_container_by_idname(dotbot_idname) def delete_dotbot_by_container_id(self, container_id: str) -> None: """ Soft-delete a DotBot. :param container_id: DotBot container ID """ self.mongo.dotbot.update_one( {"_id": ObjectId(str(container_id))}, {"$set": { "deleted": 1, "updatedAt": datetime.datetime.utcnow() }}) def delete_dotbot_by_idname(self, dotbot_idname: str) -> None: """ Soft-delete a DotBot. :param dotbot_idname: DotBot ID """ self.mongo.dotbot.update_one( { '$or': [{ 'dotbot.id': dotbot_idname }, { 'dotbot.name': dotbot_idname }] }, {"$set": { "deleted": 1, "updatedAt": datetime.datetime.utcnow() }}) ### DOTBOT def marshall_dotbot(self, result) -> DotBot: """ Marshall a dotbot. :param result: A mongodb document representing a dotbot. :return: DotBot instance """ dotbot = DotBot() dotbot.owner_name = result['ownerName'] dotbot.name = result['name'] dotbot.bot_id = result['botId'] dotbot.title = result['title'] dotbot.chatbot_engine = result['chatbotEngine'] dotbot.per_use_cost = result['perUseCost'] dotbot.per_month_cost = result['perMonthCost'] dotbot.updated_at = result['updatedAt'] try: dotbot.tts = result['tts'] except KeyError: dotbot.tts = {} return dotbot def find_one_dotbot(self, filters: dict) -> DotBot: """ Retrieve a dotbot by filters. :param filters: Dictionary with matching conditions. :return: DotBot instance or None if not found. """ result = self.mongo.greenhouse_dotbots.find_one(filters) if not result: return None return self.marshall_dotbot(result) def find_dotbots(self, filters: dict) -> list: """ Retrieve a list of dotbots. :param filters: Dictionary with matching conditions. :return: List of dotbots """ results = self.mongo.greenhouse_dotbots.find(filters) dotbots = [] for result in results: dotbots.append(self.marshall_dotbot(result)) return dotbots def find_dotbot_by_bot_id(self, bot_id: str) -> DotBot: return self.find_one_dotbot({'botId': bot_id}) ### publisher_bot def find_publisherbot_by_publisher_token(self, pub_token: str): return self.find_one_publisherbot({'token': pub_token}) def find_publisherbots_by_channel(self, channel: str) -> list: field = 'channels.' + channel return self.find_publisherbots({field: {'$exists': True}}) def find_one_publisherbot(self, filters: dict) -> PublisherBot: """ Retrieve a publisherbot by filters. :param filters: Dictionary with matching conditions. :return: PublisherBot instance or None if not found. """ result = self.mongo.greenhouse_publisher_bots.find_one(filters) if not result: return None return self.marshall_publisherbot(result) def find_publisherbots(self, filters: dict) -> list: results = self.mongo.greenhouse_publisher_bots.find(filters) publisherbots = [] for result in results: publisherbots.append(self.marshall_publisherbot(result)) return publisherbots def marshall_publisherbot(self, result) -> PublisherBot: pub_bot = PublisherBot() pub_bot.id = result['subscriptionId'] pub_bot.token = result['token'] pub_bot.publisher_name = result['publisherName'] pub_bot.bot_id = result['botId'] pub_bot.bot_name = result['botName'] pub_bot.subscription_type = result['subscriptionType'] pub_bot.updated_at = result['updatedAt'] pub_bot.channels = result['channels'] pub_bot.services = result['services'] try: pub_bot.predefined_vars = result['predefined_vars'] except KeyError: pub_bot.predefined_vars = {} return pub_bot ### DOTFLOWS def marshall_dotflow(self, result) -> DotFlowContainer: """ Marshall a DotFlowContainer. :param result: A mongodb document representing a DotFlow. :return: DotFlow instance """ dotflow_container = DotFlowContainer() if result.get('dotflow'): dotflow_container.dotflow = result['dotflow'] if result.get('dotbotId'): dotflow_container.dotbot = self.find_dotbot_container_by_idname( result['dotbotId']) if result.get('createdAt'): dotflow_container.createdAt = result['createdAt'] if result.get('updatedAt'): dotflow_container.updatedAt = result['updatedAt'] return dotflow_container def find_dotflows(self, filters: dict, projection: dict = None) -> list: """ Retrieve a list of DotFlows. :param filters: Dictionary with matching conditions. :param projection: Dictionary with projection setting. :return: List of dotflows. """ results = self.mongo.dotflow.find(filters, projection) dotflows = [] for result in results: dotflows.append(self.marshall_dotflow(result)) return dotflows def find_one_dotflow(self, filters: dict) -> DotFlowContainer: """ Retrieve a DotFlow by filters. :param filters: Dictionary with matching conditions. :return: DotFlow instance or None if not found. """ result = self.mongo.dotflow.find_one(filters) if not result: return None return self.marshall_dotflow(result) def find_dotflow_by_container_id(self, container_id) -> DotFlowContainer: """ Retrieve a dotflow by its ID. :param container_id: DotFlow ID :return: DotFlow instance or None if not found. """ return self.find_one_dotflow({"_id": ObjectId(str(container_id))}) def find_dotflow_by_idname(self, dotflow_idname) -> DotFlowContainer: """ Retrieve a DotFlowContainer object by its ID or name. :param dotflow_idname: DotFlow ID or name :return: DotFlowContainer instance or None if not found. """ return self.find_one_dotflow({ '$or': [{ 'dotflow.id': dotflow_idname }, { 'dotflow.name': dotflow_idname }] }) def find_dotflow_by_node_id(self, dotbot_id: str, node_id: str) -> DotFlowContainer: """ Retrieve a DotFlowContainer object containing the specified node id :param dotbot_id: DotBot ID :param node_id: Node ID :return: DotFlowContainer instance or None if not found. """ return self.find_one_dotflow( {'$and': [{ 'dotbotId': dotbot_id }, { 'dotflow.nodes.id': node_id }]}) def find_node_by_id(self, dotbot_id: str, node_id: str) -> dict: """ Retrieve a node by its id. :param dotbot_id: DotBot ID. :param node_id: Node ID. :return: """ dfc = self.find_dotflow_by_node_id(dotbot_id, node_id) if not dfc: return None for n in dfc.dotflow['nodes']: if n['id'] == node_id: return n def find_dotflows_by_dotbot_idname(self, dotbot_idname: str, fields: list = []) -> list: """ Retrieve a list of DotFlowContainer objects by DotBot id :param dotbot_idname: DotBot id :param fields: DotFlowContainer fields to project :return: List of DotFlowContainer objects """ # we don't know if it's id or name. retrieve dotbot anyway to get id by id or name dotbot_container = self.find_dotbot_container_by_idname(dotbot_idname) query = {'dotbotId': dotbot_container.dotbot['id']} projection = DotRepository.get_projection_from_fields(fields) return self.find_dotflows(query, projection) def find_dotflows_by_context(self, dotbot_id: str, context: str) -> list: """ Retrieve a list of DotFlow2 tagged with the specified context :param dotbot_id: DotBot ID :param context: Context :return: List of DotFlow2 objects """ # Get flows with nodes with the wanted context query = { "$and": [{ "dotbotId": dotbot_id }, { "dotflow.nodes": { "$elemMatch": { "context": context } } }] } projection = {"dotflow.nodes": 1} dotflows = self.find_dotflows(query, projection) # Get nodes with the context context_nodes = [] for df in dotflows: for n in df.dotflow['nodes']: if context in n.get('context', []): context_nodes.append(n) return context_nodes def create_dotflow(self, dotflow: dict, dotbot: dict) -> DotFlowContainer: """ Create a new dotflow. :param name: Unique dotflow name in dotbot. :param flow: DotFlow code as a JSON string. :param dotbot: A valid dotbot. :return: DotFlow created. """ oid = ObjectId() if not dotflow.get('id'): # Insert oid on dotflow id if not set dotflow['id'] = str(oid) param = { '_id': oid, 'dotflow': dotflow, 'dotbotId': dotbot['id'], 'createdAt': datetime.datetime.utcnow(), 'updatedAt': datetime.datetime.utcnow() } self.mongo.dotflow.insert_one(param) return self.find_dotflow_by_container_id( oid) #TODO maybe this should be done from the api? def update_dotflow_by_container_id(self, container_id: str, dotflow: dict) -> DotFlowContainer: """ Update a dotflow. :param container_id: DotFlowContainer ID :param name: Unique dotflow name in dotbot. :return: DotFlow updated. """ # check updatedAt for mid-air collisions self.mongo.dotflow.update_one({"_id": ObjectId(str(container_id))}, { "$set": { "dotflow": dotflow, "updatedAt": datetime.datetime.utcnow() } }) return self.find_dotflow_by_container_id(container_id) def update_dotflow_by_idname(self, dotflow_idname: str, dotflow: dict) -> DotFlowContainer: """ Update a dotflow. :param dotflow_idname: DotFlow ID :param name: Unique dotflow name in dotbot. :return: DotFlow updated. """ # check updatedAt for mid-air collisions self.mongo.dotflow.update_one( { '$or': [{ 'dotflow.id': dotflow_idname }, { 'dotflow.name': dotflow_idname }] }, { "$set": { "dotflow": dotflow, "updatedAt": datetime.datetime.utcnow() } }) return self.find_dotflow_by_idname(dotflow_idname) def delete_dotflow_by_container_id(self, container_id: str) -> None: """ Delete a dotflow. :param container_id: DotFlowContainer ID """ self.mongo.dotflow.delete_one({"_id": ObjectId(str(container_id))}) def delete_dotflow_by_idname(self, dotflow_idname: str) -> None: """ Delete a dotflow. :param dotflow_idname: DotFlow ID """ self.mongo.dotflow.delete_one({ '$or': [{ 'dotflow.id': dotflow_idname }, { 'dotflow.name': dotflow_idname }] }) def find_dotflow_formids(self, dotbot_id: str) -> list: """ Retrieve a list of form-ids used in the dotbot's dotflows :param dotbot_id :return: list if forms ids """ dotflows = self.mongo.dotflow.find({ "$and": [{ "flow.nodes.formId": { "$exists": True } }, { "_id": dotbot_id }] }) nodes = [] for f in dotflows: nodes += f['dotflow']['nodes'] formids = set() for n in nodes: if n.get('formId'): formids.add(n['formId']) return list(formids) def find_dotflow_fieldids(self, dotbot_id: str) -> list: """ Retrieve a list of field-ids used in the dotbo's dotflows :param dotbot_id :return: list of field-ids """ dotflows = self.mongo.dotflow.find({ "$and": [{ "dotflow.nodes.fieldId": { "$exists": True } }, { "_id": dotbot_id }] }) nodes = [] for f in dotflows: nodes += f['dotflow']['nodes'] fieldids = set() for n in nodes: if n.get('fieldId'): fieldids.add(n['fieldId']) return list(fieldids) ## REMOTE APIS def marshall_remote_api(self, result) -> RemoteAPI: """ Marshall a RemoteAPI. :param result: A mongodb document representing a dotbot. :return: DotBot instance """ rapi = RemoteAPI() rapi.name = result['name'] rapi.category = result['category'] rapi.function_name = result['function_name'] rapi.url = result['url'] rapi.method = result['method'] rapi.headers = result.get('headers', {}) rapi.predefined_vars = result.get('predefined_vars', {}) rapi.mapped_vars = result.get('mapped_vars', []) rapi.cost = result['cost'] return rapi def find_remote_api_by_id(self, remote_api_id) -> dict: """ Retrieve a remote api doc """ if isinstance(remote_api_id, str): filter = {"_id": ObjectId(remote_api_id)} else: obj_ids = list(map(lambda x: ObjectId(x), remote_api_id)) filter = {"_id": {"$in": obj_ids}} rapis = [] results = self.mongo.remote_apis.find(filter) for result in results: rapis.append(self.marshall_remote_api(result)) return rapis ## WATSON ASSISTANT SESSION AND CONTEXT def get_watson_assistant_session(self, user_id: str, bot_id: str): """ Returns user session id and context :param: user_id: A string with user id :param bot_id: A string with bot id :return: A dict """ r = self.mongo.watson_assistant_bot_data.find_one({ 'user_id': user_id, 'bot_id': bot_id }) return r def set_watson_assistant_session(self, user_id: str, bot_id: str, session_id: str, context: dict = {}): """ Stores user session id and context :param user_id: A string with user id :param bot_id: A string with bot id :param session_id: A string with session id :param context: A dict with context """ self.mongo.watson_assistant_bot_data.update( { 'user_id': user_id, 'bot_id': bot_id }, { 'user_id': user_id, 'bot_id': bot_id, 'session_id': session_id, 'context': context }, upsert=True) ## MICROSOFT DIRECTLINE SESSION def get_directline_session(self, user_id: str, bot_id: str): """ Returns conversation id and watermark :param: user_id: A string with user id :param bot_id: A string with bot id :return: A dict """ r = self.mongo.directline_bot_data.find_one({ 'user_id': user_id, 'bot_id': bot_id }) if r: return [r['conversation_id'], r['watermark']] return None def set_directline_session(self, user_id: str, bot_id: str, conversation_id: str, watermark: int): """ Stores conversation id and watermark :param user_id: A string with user id :param bot_id: A string with bot id :param conversation_id: A string with conversation id :param watermar: An integer with watermark """ self.mongo.directline_bot_data.update( { 'user_id': user_id, 'bot_id': bot_id }, { 'user_id': user_id, 'bot_id': bot_id, 'conversation_id': conversation_id, 'watermark': watermark }, upsert=True) ## AZURE ACTIVE DIRECTORY ACCESS TOKEN def get_azure_ad_access_token(self, bot_id: str): r = self.mongo.azure_ad_access_token.find_one({'bot_id': bot_id}) return r def set_azure_ad_access_token(self, bot_id: str, access_token: str, expire_date: int): self.mongo.azure_ad_access_token.update({'bot_id': bot_id}, { 'bot_id': bot_id, 'access_token': access_token, 'expire_date': expire_date }, upsert=True) ## GREENHOUSE SUBSCRIPTION_PAYMENTS def get_last_payment_date_by_subscription_id(self, subscription_id): res = self.mongo.subscription_payments.find_one( {'subscriptionId': ObjectId(subscription_id)}) if res: return res['lastPaymentDate'] return None ## PANDORABOTS SESSIONID AND CLIENT_NAME def get_pandorabots_session(self, bot_id: str, user_id: str): """ Returns user sessionid and client_name :param: user_id: A string with user id :return: A dict """ r = self.mongo.pandorabots_bot_data.find_one({ 'bot_id': bot_id, 'user_id': user_id }) return r def set_pandorabots_session(self, bot_id: str, user_id: str, sessionid: str, client_name: str): """ Stores user session id and context :param user_id: A string with user id :param session_id: A string with session id :param context: A dict with context """ self.mongo.pandorabots_bot_data.update( { 'bot_id': bot_id, 'user_id': user_id }, { 'bot_id': bot_id, 'user_id': user_id, 'sessionid': sessionid, 'client_name': client_name }, upsert=True)
class TemplateEngineTemplator(): """.""" def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. """ self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.logger = None def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter( logging.getLogger('pipeline.templator'), self, self.core, 'Templetor') def get_functions(self): """ Adds custom functions to template @TODO this should be initialized just once at runtime, not at every render!! :return: """ c_functions = {} for f_name in self.core.functions_map: # register template functions from extensions self.logger.debug('Adding template custom function "' + f_name + '"') c_functions[f_name] = getattr(self.core.bbot, f_name) return c_functions def render(self, string: str) -> str: """ Renders any string with configured bbot custom functions and bot session vars """ # We still need a way to know if a string is a template or not, but Templator don't need enclosing # So for Templator, just enclose the whole string with {{ }} for BBot to know it is a template if re.search('({%).*(%})|({{.*}})', string) is None: self.logger.debug('Nothing to render') return string string = string.replace('{{', '') string = string.replace('}}', '') string = string.replace('{%', '') string = string.replace('%}', '') session_vars = {} if hasattr(self.core.bot, 'session'): session_vars = self.core.bot.session.get_var(self.core.bot.user_id) t_globals = session_vars # add predefined vars from publishers if hasattr(self.dotbot, 'botsubscription'): if hasattr(self.dotbot.botsubscription, 'predefined_vars'): t_globals = { **t_globals, **self.dotbot.botsubscription.predefined_vars } # get custom functions from extensions c_functions = self.get_functions() t_globals = {**t_globals, **c_functions} self.logger.debug('Rendering template: "' + str(string) + '"') templator_obj = Template(string, globals=t_globals) try: response = str(templator_obj()) except NameError as e: err_msg = 'Template error: ' + str(e) self.core.logger.debug(err_msg) raise BBotException({'message': err_msg}) if response[ -1:] == '\n': # Templator seems to add a trailing \n, remove it response = response[:-1] self.logger.debug('Template response: "' + response + '"') if response.find('<function BBotFunctionsProxy') is not -1: self.logger.error( 'Templator returned an invalid response. Botdev forgot to escape $?' ) response = '<TEMPLATE RENDERING ERROR. CHECK DEBUG DATA>' # @TODO add debug data in context to the instruction executed return response def process(self): """ Runs as pipeline process """ for k, r in enumerate(self.core.response['output']): response_type = list(r)[0] if response_type == 'text': #@TODO this should traverse the whole dict not just text response = r['text'] self.core.response['output'][k]['text'] = self.render(response)
class WatsonAssistant(ChatbotEngine): """ BBot engine that calls external program. """ def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. :param config: Configuration values for the instance. """ super().__init__(config, dotbot) self.dotdb = None def init(self, core: BBotCore): """ Initializebot engine """ super().init(core) self.logger = BBotLoggerAdapter(logging.getLogger('watson_cbe'), self, self.core) # Set up Assistant service. authenticator = IAMAuthenticator( self.dotbot.chatbot_engine['iamApikey']) self.service = ibm_watson.AssistantV2(authenticator=authenticator, version='2019-02-28') self.service.set_service_url(self.dotbot.chatbot_engine['url']) self.logger.debug("Connection: " + str(self.service)) def get_response(self, request: dict) -> dict: """ Return a response based on the input data. :param request: A dictionary with input data. :return: A response to the input data. """ self.request = request input_text = request['input']['text'] user_id = request['user_id'] session = self.get_bot_session(user_id) session_id = session['session_id'] response = {} try: response = self.get_assistant_response(session_id, request['input']) except ibm_cloud_sdk_core.api_exception.ApiException as e: # This means we are trying to use an expired session # @TODO I don't know how to test this before. try to improve it. # create a new session (but dont delete context!) and try again self.logger.debug('Session has timed out. Try to create a new one') session = self.get_bot_session(user_id, True) session_id = session['session_id'] response = self.get_assistant_response(session_id, request['input']) self.logger.debug("Response: " + str(response)) output = "" if response['output']['generic']: if response['output']['generic'][0]['response_type'] == 'text': output = response['output']['generic'][0]['text'] self.core.bbot.text(output) def get_assistant_response(self, session_id: str, rinput: dict): """ Returns response from bot engine :param session_id: The session id :param rinput: The request dict :return: A dict """ return self.service.message(self.dotbot.chatbot_engine['assistantId'], session_id, input={ 'text': rinput['text'] }).get_result() def get_bot_session(self, user_id: str, renew: bool = False): """ Returns session data both session id and context If there is no session on db we create one :param user_id: A string with the user id :param renew: A bool to indicate if we need to renew the session id (watson asisstant has a timeout and we have to get a new one when that happens) :return: A dict """ session = self.dotdb.get_watson_assistant_session(user_id) or { 'session_id': None, 'context': {} } if session: self.logger.debug("Found old session: " + str(session)) if renew: session['session_id'] = None if not session.get('session_id'): session_id = self.service.create_session( assistant_id=self.dotbot.chatbot_engine['assistantId'] ).get_result()['session_id'] session['session_id'] = session_id self.logger.debug("Created new session: " + session_id) self.dotdb.set_watson_assistant_session( user_id, session_id, session['context'] ) # context might have data if we are renewing session return session
class TokenManagerSeedWallet(): web3 = None def __init__(self, config: dict, dotbot: dict) -> None: """Initializes class""" self.config = config self.dotbot = dotbot self.logger_level = '' self.core = None self.seed_wallet_api_key = '' self.seed_wallet_url = '' self.logger = BBotLoggerAdapter(logging.getLogger('token_seedwallet'), self, self, '') def init(self, core): """Initializes some values""" pass def transfer(self, fromUsername: str, toUsername: str, amount: float, credential: str = ''): """ Transfers tokens from one user to another :param fromUsername: (string) Username source :param toUsername: (string) Username destination :param amount: (float) amount of tokens to be transfered :param credential: (string) optional. Credentials needed to move funds from source (usually passphrase or private key) :returns: (string) TX hash """ self.logger.debug('Transfering ' + str(amount) + ' SEED from user ' + fromUsername + ' to user ' + toUsername) payload = { 'username': fromUsername, 'to': toUsername, 'amount': str(amount) } r = self.do_request('post', 'send', payload) try: aw = r.json() except json.decoder.JSONDecodeError: raise Exception('Seed Wallet error response: ' + r.text) if r.status_code != 200: # check if the error is insufficient funds if r.status_code == 422 and aw['errors'].get( 'amount', {} ).get('message') == 'domain.wallet.validation.insufficient_funds': raise TokenManagerInsufficientFundsException() raise Exception('Seed Wallet error response: ' + str(aw)) self.logger.debug('Transaction hash: ' + str(aw['transactionId'])) return aw['transactionId'] def get_balance(self, username: str): """ """ self.logger.debug('Getting user balance') r = self.do_request('get', 'balance', {'username': username}) aw = r.json() if r.status_code != 200: raise Exception('Seed Wallet error response: ' + str(aw)) return float(aw['balance']) def do_request(self, type: str, method: str, payload: str = dict): """ """ headers = { 'API-INTEGRATION-KEY': self.seed_wallet_api_key, "Content-type": "application/json" } self.logger.debug('Requesting to Seed Wallet at url ' + self.seed_wallet_url + method) if type == 'post': r = requests.post(self.seed_wallet_url + method, json=payload, headers=headers) if type == 'get': r = requests.get(self.seed_wallet_url + method, params=payload, headers=headers) self.logger.debug('Seed Wallet response: Code: ' + str(r.status_code) + ': ' + str(r.text[0:300])) return r
class RemoteAPIs(): """Calls remote API endpoints""" def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. """ self.config = config self.dotbot = dotbot # vars set from plugins self.logger_level = '' self.request_timeout = 1 self.dotdb = None self.core = None def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('ext.r_services'), self, self.core.bot, '$call_api') # get services for the bot services = self.get_bot_services() for serv in services: self.logger.debug('Register service ' + serv['name']) self.logger.debug(str(serv)) fmap = { 'owner_name': serv['ownerName'], 'cost': serv['cost'], 'subscription_type': serv['subscriptionType'], 'subscription_id': serv['subscriptionId'], 'register_enabled': True } if serv['url'] is not '': fmap = { **fmap, **{ 'object': self, 'method': serv['function_name'], 'url': serv['url'], 'request_method': serv['method'], 'timeout': serv['timeout'], 'predefined_vars': serv['predefined_vars'], 'headers': serv['headers'], 'user': serv.get('user'), 'passwd': serv.get('passwd'), 'mapped_vars': serv['mapped_vars'], } } core.register_function(serv['function_name'], fmap) def get_bot_services(self): return self.dotbot.services def __getattr__(self, fname): def function(*args, **kwargs): r_api_data = self.core.functions_map[fname] self.logger.debug('Calling remote API ' + fname + ' args: ' + str(args) + ' - metadata: ' + str(r_api_data)) auth = None if r_api_data.get('user'): auth = (r_api_data['user'], r_api_data['passwd']) # map args to variables c = 0 params = {} for mv in r_api_data['mapped_vars']: try: params[mv] = self.core.resolve_arg(args[c]) c += 1 except IndexError: raise BBotException({ 'code': 250, 'function': fname, 'arg': c, 'message': 'Parameter ' + str(c) + ' is missing' }) params = {**params, **r_api_data['predefined_vars']} # interpolate args to url url = r_api_data['url'] c = 0 for arg in args[0]: url = url.replace('{{' + str(c) + '}}', str(args[c])) c += 1 try: if r_api_data['request_method'] == 'get': r = requests.get( url, params=params, headers=r_api_data['headers'], timeout=self.request_timeout or r_api_data['timeout'], # <<<< custom per service? auth=auth, allow_redirects=True, ) else: r = requests.post(url, data=params, headers=r_api_data['headers'], timeout=self.request_timeout or r_api_data['timeout'], auth=auth, allow_redirects=True) self.logger.debug('Response:' + str(r)) self.logger.debug('Headers: ' + str(r.headers)) if r.status_code == requests.codes.ok: if 'application/json' in r.headers.get( 'Content-Type' ) or 'application/javascript' in r.headers.get( 'Content-Type'): return r.json() else: # default return r.text # On error r.raise_for_status() except Exception as e: self.logger.debug( str(e) + "\n" + str(traceback.format_exc()) ) # errors in remote apis are not critical for us. This should be alerted to service dev if os.environ['BBOT_ENV'] == 'development': raise BBotExtensionException(str(e), BBotCore.FNC_RESPONSE_ERROR) else: return None return function
class WeatherReport(): """Returns Weather Report""" def __init__(self, config: dict, dotbot: dict) -> None: """ Initialize the plugin. """ self.config = config self.dotbot = dotbot # vars set from plugins self.accuweather_api_key = '' self.logger_level = '' self.core = None self.logger = None def init(self, core: BBotCore): """ :param bot: :return: """ self.core = core self.logger = BBotLoggerAdapter(logging.getLogger('core_fnc.weather'), self, self.core.bot, '$weather') self.method_name = 'weather' self.accuweather_text = 'Weather forecast provided by Accuweather' self.accuweather_image_url = 'https://static.seedtoken.io/AW_RGB.png' core.register_function( 'weather', { 'object': self, 'method': self.method_name, 'cost': 0.1, 'register_enabled': True }) # we register this to add accuweather text even when result is cached from extensions_cache decorator smokesignal.on(BBotCore.SIGNAL_CALL_BBOT_FUNCTION_AFTER, self.add_accuweather_text) @BBotCore.extensions_cache def weather(self, args, f_type): """ Returns weather report @TODO return forecast based on args[1] date :param args: :param f_type: :return: """ try: location = self.core.resolve_arg(args[0], f_type) except IndexError: raise BBotException({ 'code': 0, 'function': 'weather', 'arg': 0, 'message': 'Location is missing.' }) try: date = args[1] except IndexError: date = 'today' # optional. default 'today' self.logger.info(f'Retrieving weather for {location}') st = self.search_text(location) if not st: self.logger.info("Location not found. Invalid location") return { 'text': '<No weather data or invalid location>', #@TODO should raise a custom exception which will be used for flow exceptions 'canonicalLocation': location } location_key = st[0].get('Key', None) self.logger.debug("Accuweather Location Key: " + location_key) canonical_location = st[0]['LocalizedName'] + ', ' + st[0]['Country'][ 'LocalizedName'] self.logger.debug('Canonical location: ' + canonical_location) self.logger.debug('Requeting Accuweather current conditions...') r = requests.get( f'http://dataservice.accuweather.com/currentconditions/v1/{location_key}?apikey={self.accuweather_api_key}&details=false' ) self.logger.debug('Accuweather response: ' + str(r.json())[0:300]) if r.status_code == 200: aw = r.json() return { 'text': aw[0]['WeatherText'], 'temperature': { 'metric': str(aw[0]['Temperature']['Metric']['Value']), 'imperial': str(aw[0]['Temperature']['Imperial']['Value']) }, 'canonicalLocation': canonical_location } err_msg = r.json()['fault']['faultstring'] self.logger.critical(err_msg) raise BBotExtensionException(err_msg, BBotCore.FNC_RESPONSE_ERROR) def search_text(self, location): # get locationkey based on provided location self.logger.info(f'Requesting Accuweather location key...') r = requests.get( f'http://dataservice.accuweather.com/locations/v1/search?apikey={self.accuweather_api_key}&q={location}&details=false' ) self.logger.debug('Accuweather response: ' + str(r.json())[0:300]) if r.status_code == 200: return r.json() err_msg = r.json()['fault']['faultstring'] self.logger.critical(err_msg) raise BBotExtensionException(err_msg, BBotCore.FNC_RESPONSE_ERROR) def add_accuweather_text(self, data): # check if call is made from a weather call if data['name'] is self.method_name: # check if the call was successful if data['response_code'] is BBotCore.FNC_RESPONSE_OK: # check if text is already added if not self.core.bbot.outputHasText(self.accuweather_text): # adds accuweather logo to the bots response self.core.bbot.text(self.accuweather_text) self.core.bbot.image(self.accuweather_image_url)