diff --git a/bots/servusai/kik/aiml/sraix.aiml b/bots/servusai/kik/aiml/sraix.aiml new file mode 100644 index 000000000..4ab504af7 --- /dev/null +++ b/bots/servusai/kik/aiml/sraix.aiml @@ -0,0 +1,11 @@ + + + + + * + + + + \ No newline at end of file diff --git a/bots/servusai/kik/config.yaml b/bots/servusai/kik/config.yaml new file mode 100644 index 000000000..1d65964db --- /dev/null +++ b/bots/servusai/kik/config.yaml @@ -0,0 +1,60 @@ +bot: + license_keys: $BOT_ROOT/config/license.keys + + prompt: ">>>" + + initial_question: Hi, how can I help you today? + default_response: Sorry, I don't have an answer for that! + default_response_srai: YEMPTY + empty_string: YEMPTY + exit_response: So long, and thanks for the fish! + exit_response_srai: YEXITRESPONSE + + override_properties: true + +brain: + + # Overrides + overrides: + allow_system_aiml: false + allow_learn_aiml: false + allow_learnf_aiml: false + + # Defaults + defaults: + default-get: unknown + default-property: unknown + default-map: unknown + + # Nodes + nodes: + pattern_nodes: ../../ybot/config/pattern_nodes.conf + template_nodes: ../../ybot/config/template_nodes.conf + + files: + aiml: + files: $BOT_ROOT/aiml + extension: .aiml + directories: true + conversation: + file: /tmp/multibot-conversation.csv + format: csv + delete_on_start: true + properties: $BOT_ROOT/config/properties.txt + preprocessors: $BOT_ROOT/config/preprocessors.conf + postprocessors: $BOT_ROOT/config/postprocessors.conf + + services: + PROGRAMY: + classname: programy.services.programy.ProgramyRESTService + method: GET + host: 0.0.0.0 + port: 8989 + url: /api/v1.0/ask + +kik: + bot_name: servusai + webhook: https://4264d3b1.ngrok.io/incoming + host: 127.0.0.1 + port: 5000 + debug: false diff --git a/bots/servusai/kik/config/postprocessors.conf b/bots/servusai/kik/config/postprocessors.conf new file mode 100644 index 000000000..c3e82e645 --- /dev/null +++ b/bots/servusai/kik/config/postprocessors.conf @@ -0,0 +1,7 @@ +programy.processors.post.mergechinese.MergeChinesePostProcessor +programy.processors.post.denormalize.DenormalizePostProcessor +programy.processors.post.formatpunctuation.FormatPunctuationProcessor +programy.processors.post.formatnumbers.FormatNumbersPostProcessor +programy.processors.post.multispaces.RemoveMultiSpacePostProcessor +programy.processors.post.removehtml.RemoveHTMLPostProcessor +programy.processors.post.consoleformat.ConsoleFormatPostProcessor \ No newline at end of file diff --git a/bots/servusai/kik/config/preprocessors.conf b/bots/servusai/kik/config/preprocessors.conf new file mode 100644 index 000000000..d9ac8e134 --- /dev/null +++ b/bots/servusai/kik/config/preprocessors.conf @@ -0,0 +1,3 @@ +programy.processors.pre.normalize.NormalizePreProcessor +programy.processors.pre.removepunctuation.RemovePunctuationPreProcessor +programy.processors.pre.splitchinese.SplitChinesePreProcessor \ No newline at end of file diff --git a/bots/servusai/kik/config/properties.txt b/bots/servusai/kik/config/properties.txt new file mode 100644 index 000000000..d490d29ca --- /dev/null +++ b/bots/servusai/kik/config/properties.txt @@ -0,0 +1,7 @@ +fullname:Y-Bot + +birthdate:March 14, 2017 + +grammar_version:1.0.0 +app_version: 1.0.0 + diff --git a/bots/servusai/kik/exec_ngrok.sh b/bots/servusai/kik/exec_ngrok.sh new file mode 100644 index 000000000..4cd04d5bd --- /dev/null +++ b/bots/servusai/kik/exec_ngrok.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +ngrok http 5000 diff --git a/bots/servusai/kik/kik.sh b/bots/servusai/kik/kik.sh new file mode 100755 index 000000000..34aa5cb9b --- /dev/null +++ b/bots/servusai/kik/kik.sh @@ -0,0 +1,8 @@ +#! /bin/sh + +clear + +export PYTHONPATH=../../../src:. + +python3 ../../../src/programy/clients/kik_client.py --config ./config.yaml --cformat yaml --logging ../../y-bot/logging.yaml + diff --git a/bots/servusai/kik/servusai-kik.service b/bots/servusai/kik/servusai-kik.service new file mode 100755 index 000000000..b37e84c90 --- /dev/null +++ b/bots/servusai/kik/servusai-kik.service @@ -0,0 +1,11 @@ +[Unit] +Description=Y-Bot Line Client + +[Service] +User=ubuntu +ExecStart=/opt/program-y/bots/servusai/kik/servusai-kik.sh +Restart=always + +[Install] +WantedBy=multi-user.target + diff --git a/bots/servusai/kik/servusai-kik.sh b/bots/servusai/kik/servusai-kik.sh new file mode 100755 index 000000000..708f81afc --- /dev/null +++ b/bots/servusai/kik/servusai-kik.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +# This file is for use on Servusai.com AWS Server + +export PYTHONPATH=/opt/program-y/src:. + +cd /opt/program-y + +python3 /opt/program-y/src/programy/clients/kik_client.py --config /opt/program-y/bots/servusai/kik/config.yaml --cformat yaml --logging /opt/program-y/bots/y-bot/logging.yaml + diff --git a/bots/servusai/line/servusai-line.service b/bots/servusai/line/servusai-line.service index 5f008295f..6f9cfbfb8 100755 --- a/bots/servusai/line/servusai-line.service +++ b/bots/servusai/line/servusai-line.service @@ -3,7 +3,7 @@ Description=Y-Bot Line Client [Service] User=ubuntu -ExecStart=/opt/program-y/bots/servusai/line/servusai-twilio.sh +ExecStart=/opt/program-y/bots/servusai/line/servusai-kik.sh Restart=always [Install] diff --git a/bots/servusai/viber/servusai-viber.service b/bots/servusai/viber/servusai-viber.service index cff64988a..7859cf9ce 100755 --- a/bots/servusai/viber/servusai-viber.service +++ b/bots/servusai/viber/servusai-viber.service @@ -3,7 +3,7 @@ Description=Y-Bot Viber Client [Service] User=ubuntu -ExecStart=/opt/program-y/bots/servusai/viber/servusai-twilio.sh +ExecStart=/opt/program-y/bots/servusai/viber/servusai-kik.sh Restart=always [Install] diff --git a/bots/y-bot/config.yaml b/bots/y-bot/config.yaml index 4ad393324..d9898eb5a 100644 --- a/bots/y-bot/config.yaml +++ b/bots/y-bot/config.yaml @@ -253,3 +253,10 @@ line: host: 127.0.0.1 port: 5000 debug: false + +kik: + bot_name: servusai + webhook: https://4264d3b1.ngrok.io/incoming + host: 127.0.0.1 + port: 5000 + debug: false diff --git a/bots/y-bot/y-bot-kik.cmd b/bots/y-bot/y-bot-kik.cmd new file mode 100755 index 000000000..945c9dd9e --- /dev/null +++ b/bots/y-bot/y-bot-kik.cmd @@ -0,0 +1,9 @@ +@echo off + +CLS + +mkdir .\temp + +SET PYTHONPATH=..\..\src;. + +python ..\..\src\programy\clients\kik_client.py --config .\config.windows.yaml --cformat yaml --logging .\logging.windows.yaml diff --git a/bots/y-bot/y-bot-kik.sh b/bots/y-bot/y-bot-kik.sh new file mode 100755 index 000000000..a81000ced --- /dev/null +++ b/bots/y-bot/y-bot-kik.sh @@ -0,0 +1,8 @@ +#! /bin/sh + +clear + +export PYTHONPATH=../../src:. + +python3 ../../src/programy/clients/kik_client.py --config ./config.yaml --cformat yaml --logging ./logging.yaml + diff --git a/requirements.txt b/requirements.txt index d1c39e14b..ff60dd4a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ slackclient==1.1.2 redis==2.10.6 viberbot==1.0.11 line-bot-sdk==1.5.0 +kik==1.5.0 # Uncomment this line on Osx or Linux if you want to use sanic rest client # Sanic is not supported on Windows ! diff --git a/src/programy/clients/kik_client.py b/src/programy/clients/kik_client.py new file mode 100644 index 000000000..4993f7bc8 --- /dev/null +++ b/src/programy/clients/kik_client.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-17 Keith Sterling http://www.keithsterling.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +# https://kik.readthedocs.io/en/latest/user.html#installation + +import logging + +from flask import Flask, request, abort, Response + +from kik import KikApi, Configuration +from kik.messages import messages_from_json, TextMessage + +from programy.clients.client import BotClient +from programy.config.sections.client.kik_client import KikConfiguration + + +KIK_CLIENT = None + + +class KikBotClient(BotClient): + + def __init__(self, argument_parser=None): + BotClient.__init__(self, "kik", argument_parser) + + self.get_tokens() + + self.create_kik_bot() + + if logging.getLogger().isEnabledFor(logging.DEBUG): + logging.debug("Kik Client is running....") + + def set_environment(self): + self.bot.brain.properties.add_property("env", 'kik') + + def get_client_configuration(self): + return KikConfiguration() + + def get_tokens(self): + self._bot_api_key = self.bot.license_keys.get_key("KIK_BOT_API_KEY") + + def ask_question(self, clientid, question): + response = "" + try: + response = self.bot.ask_question(clientid, question) + except Exception as e: + print(e) + return response + + def create_kik_bot(self): + self._kik_bot = KikApi(self.configuration.client_configuration.bot_name, self._bot_api_key) + self._kik_bot.set_configuration(Configuration(webhook=self.configuration.client_configuration.webhook)) + + def handle_text_message(self, message): + question = message.body + clientid = message.from_user + + answer = self.ask_question(clientid, question) + + self._kik_bot.send_messages([ + TextMessage( + to=message.from_user, + chat_id=message.chat_id, + body=answer + ) + ]) + + def handle_unknown_message(self, message): + pass + + def handle_message_request(self, request): + + messages = messages_from_json(request.json['messages']) + + for message in messages: + if isinstance(message, TextMessage): + self.handle_text_message(message) + else: + self.handle_unknown_message(message) + + def handle_incoming(self, request): + if not self._kik_bot.verify_signature(request.headers.get('X-Kik-Signature'), request.get_data()): + return Response(status=403) + + self.handle_message_request(request) + return Response(status=200) + + +APP = Flask(__name__) + + +@APP.route('/incoming', methods=['POST']) +def incoming(): + return KIK_CLIENT.handle_incoming(request) + + +if __name__ == '__main__': + + KIK_CLIENT = KikBotClient() + + print("Kik Client running on %s:%s" % (KIK_CLIENT.configuration.client_configuration.host, + KIK_CLIENT.configuration.client_configuration.port)) + + + if KIK_CLIENT.configuration.client_configuration.debug is True: + print("Kik Client running in debug mode") + + if KIK_CLIENT.configuration.client_configuration.ssl_cert_file is not None and \ + KIK_CLIENT.configuration.client_configuration.ssl_key_file is not None: + context = (KIK_CLIENT.configuration.client_configuration.ssl_cert_file, + KIK_CLIENT.configuration.client_configuration.ssl_key_file) + + print("Kik Client running in https mode") + APP.run(host=KIK_CLIENT.configuration.client_configuration.host, + port=KIK_CLIENT.configuration.client_configuration.port, + debug=KIK_CLIENT.configuration.client_configuration.debug, + ssl_context=context) + else: + print("Kik Client running in http mode, careful now !") + APP.run(host=KIK_CLIENT.configuration.client_configuration.host, + port=KIK_CLIENT.configuration.client_configuration.port, + debug=KIK_CLIENT.configuration.client_configuration.debug) diff --git a/src/programy/config/sections/client/kik_client.py b/src/programy/config/sections/client/kik_client.py new file mode 100644 index 000000000..559a664d0 --- /dev/null +++ b/src/programy/config/sections/client/kik_client.py @@ -0,0 +1,79 @@ +""" +Copyright (c) 2016-17 Keith Sterling http://www.keithsterling.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial avatarions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +from programy.config.base import BaseContainerConfigurationData + +class KikConfiguration(BaseContainerConfigurationData): + + def __init__(self): + BaseContainerConfigurationData.__init__(self, "kik") + self._bot_name = "program-y" + self._webhook = "https://localhost:5000" + self._host = "0.0.0.0" + self._port = 5000 + self._debug = False + self._workers = 4 + self._ssl_cert_file = None + self._ssl_key_file = None + + @property + def bot_name(self): + return self._bot_name + + @property + def webhook(self): + return self._webhook + + @property + def host(self): + return self._host + + @property + def port(self): + return self._port + + @property + def debug(self): + return self._debug + + @property + def workers(self): + return self._workers + + @property + def ssl_cert_file(self): + return self._ssl_cert_file + + @property + def ssl_key_file(self): + return self._ssl_key_file + + def load_configuration(self, configuration_file, bot_root): + kik = configuration_file.get_section(self.section_name) + if kik is not None: + self._bot_name = configuration_file.get_option(kik, "bot_name", missing_value="program-y") + self._webhook = configuration_file.get_option(kik, "webhook", missing_value="https://localhost:5000") + self._host = configuration_file.get_option(kik, "host", missing_value="0.0.0.0") + self._port = configuration_file.get_option(kik, "port", missing_value=5000) + self._debug = configuration_file.get_bool_option(kik, "debug", missing_value=False) + self._workers = configuration_file.get_option(kik, "workers", missing_value=4) + self._ssl_cert_file = configuration_file.get_option(kik, "ssl_cert_file") + if self._ssl_cert_file is not None: + self._ssl_cert_file = self.sub_bot_root(self._ssl_cert_file, bot_root) + self._ssl_key_file = configuration_file.get_option(kik, "ssl_key_file") + if self._ssl_key_file is not None: + self._ssl_key_file = self.sub_bot_root(self._ssl_key_file, bot_root) diff --git a/test/programytest/clients/test_kik.py b/test/programytest/clients/test_kik.py new file mode 100644 index 000000000..5507d5419 --- /dev/null +++ b/test/programytest/clients/test_kik.py @@ -0,0 +1,51 @@ +import unittest.mock + +from kik import KikApi + +from programy.clients.kik_client import KikBotClient +from programy.config.sections.client.kik_client import KikConfiguration + +from programytest.clients.arguments import MockArgumentParser + +class MockKikApi(KikApi): + + def __init__(self, bot, api_key): + pass + +class TestKikBotClient(KikBotClient): + + def __init__(self, argument_parser=None, kik_client=None): + self.test_kik_client = kik_client + self.test_question = None + KikBotClient.__init__(self, argument_parser) + + def set_question(self, question): + self.test_question = question + + def get_tokens(self): + self._bot_api_key = "KIK_BOT_API_KEY" + + def ask_question(self, sessionid, question): + if self.test_question is not None: + return self.test_question + return super(TestKikBotClient, self).ask_question(sessionid, question) + + def create_kik_bot(self): + if self.test_kik_client is not None: + return self.test_kik_client + return super(TestKikBotClient,self).create_kik_bot() + + +class KikBotClientTests(unittest.TestCase): + + def test_kik_client_init(self): + arguments = MockArgumentParser() + client = TestKikBotClient(arguments, kik_client=MockKikApi(bot="test", api_key=None)) + self.assertIsNotNone(client) + + self.assertEquals("KIK_BOT_API_KEY", client._bot_api_key) + + self.assertEquals("kik", client.bot.brain.properties.property("env")) + + self.assertIsInstance(client.get_client_configuration(), KikConfiguration) + diff --git a/test/programytest/config/sections/client/test_kik.py b/test/programytest/config/sections/client/test_kik.py new file mode 100644 index 000000000..afa460a5c --- /dev/null +++ b/test/programytest/config/sections/client/test_kik.py @@ -0,0 +1,44 @@ +import unittest + +from programy.config.file.yaml_file import YamlConfigurationFile +from programy.config.sections.client.kik_client import KikConfiguration +from programy.config.sections.client.console import ConsoleConfiguration + +class KikConfigurationTests(unittest.TestCase): + + def test_init(self): + yaml = YamlConfigurationFile() + self.assertIsNotNone(yaml) + yaml.load_from_text(""" + kik: + bot_name: testbot + webhook: https://localhost:5000 + host: 127.0.0.1 + port: 5000 + debug: false + """, ConsoleConfiguration(), ".") + + kik_config = KikConfiguration() + kik_config.load_configuration(yaml, ".") + + self.assertEqual("testbot", kik_config.bot_name) + self.assertEqual("https://localhost:5000", kik_config.webhook) + self.assertEqual("127.0.0.1", kik_config.host) + self.assertEqual(5000, kik_config.port) + self.assertEqual(False, kik_config.debug) + + def test_init_no_values(self): + yaml = YamlConfigurationFile() + self.assertIsNotNone(yaml) + yaml.load_from_text(""" + kik: + """, ConsoleConfiguration(), ".") + + kik_config = KikConfiguration() + kik_config.load_configuration(yaml, ".") + + self.assertEqual("program-y", kik_config.bot_name) + self.assertEqual("https://localhost:5000", kik_config.webhook) + self.assertEqual("0.0.0.0", kik_config.host) + self.assertEqual(5000, kik_config.port) + self.assertEqual(False, kik_config.debug)