Example #1
0
def test_webhook_remove():
    bot = VKBot(token='12345', group_id=12345)
    with patch('requests.get') as mock_get:
        mock_get.return_value.status_code = 200
        mock_get.return_value.json = lambda: {'response': {'items': [{'id': 17}, {'id': 18}]}}
        bot.remove_webhook()
        assert mock_get.call_count == 3
        assert mock_get.call_args_list[0][1]['url'].endswith('groups.getCallbackServers')
        for call_args, item_id in zip(mock_get.call_args_list[1:], [17, 18]):
            assert call_args[1]['url'].endswith('groups.deleteCallbackServer')
            assert call_args[1]['params']['server_id'] == item_id
Example #2
0
def test_send_message(mock_post: MagicMock):
    mock_post.return_value.status_code = 200
    mock_post.return_value.json = lambda: {}

    bot = VKBot(token='12345', group_id=12345)
    bot.send_message(user_id=666, text='hello', keyboard={'buttons': [[{'action': {'type': 'text', 'label': 'yay'}}]]})
    assert mock_post.called
    args, kwargs = mock_post.call_args
    assert kwargs['url'].endswith('messages.send')
    assert kwargs['data']['user_id'] == 666
    assert kwargs['data']['message'] == 'hello'
    assert kwargs['data']['access_token'] == '12345'
    assert 'group_id' not in kwargs['data']
    assert isinstance(kwargs['data']['keyboard'], str)
Example #3
0
def test_webhook_processing():
    bot = VKBot(token='12345', group_id=12345)
    bot.webhook_key = 'secret'
    assert bot.process_webhook_data({'type': 'confirmation', 'group_id': 12345}) == ('secret', 200)

    messages = []

    @bot.message_handler()
    def handle(message):
        messages.append(message)

    new_message = {'type': 'message_new', 'object': {'message': {'text': 'hello bot'}}}
    assert bot.process_webhook_data(new_message) == ('ok', 200)
    assert messages[0].text == 'hello bot'
Example #4
0
def test_webhook_set():
    bot = VKBot(token='12345', group_id=12345)
    assert bot.webhook_key is None
    with patch('requests.get') as mock_get:
        mock_get.return_value.status_code = 200
        mock_get.return_value.json = lambda: {'response': {'code': '777', 'server_id': 13}}
        bot.set_webhook(url='localhost:15777', remove_old=False)
        assert bot.webhook_key == '777'
        assert mock_get.call_count == 3
        assert mock_get.call_args_list[0][1]['url'].endswith('groups.getCallbackConfirmationCode')

        assert mock_get.call_args_list[1][1]['url'].endswith('groups.addCallbackServer')
        assert mock_get.call_args_list[1][1]['params']['secret_key'] == '777'
        assert mock_get.call_args_list[1][1]['params']['url'] == 'localhost:15777'

        assert mock_get.call_args_list[2][1]['url'].endswith('groups.setCallbackSettings')
        assert mock_get.call_args_list[2][1]['params']['server_id'] == 13
Example #5
0
def test_apply_handlers():
    bot = VKBot(token='12345', group_id=12345)
    processed1 = []
    processed2 = []
    processed3 = []
    processed4 = []

    @bot.message_handler(regexp='hello')
    def handle1(message: VKMessage):
        processed1.append(message)

    @bot.message_handler(types=['strange_type'])
    def handle2(message: VKMessage):
        processed2.append(message)

    @bot.message_handler(func=lambda x: len(x['object']['message']['text']) == 3)
    def handle3(message: VKMessage):
        processed3.append(message)

    @bot.message_handler()
    def handle4(message: VKMessage):
        processed4.append(message)

    assert len(bot.message_handlers) == 4

    bot.process_new_updates([{'type': 'message_new', 'object': {'message': {'text': 'hello bot'}}}])
    assert len(processed1) == 1
    assert len(processed2) == 0
    assert processed1[0].text == 'hello bot'

    bot.process_new_updates([{'type': 'strange_type', 'object': {'message': {'text': 'hello bot'}}}])
    assert len(processed1) == 1
    assert len(processed2) == 1

    bot.process_new_updates([{'type': 'message_new', 'object': {'message': {'text': 'wow'}}}])
    assert len(processed3) == 1

    bot.process_new_updates([{'type': 'message_new', 'object': {'message': {'text': 'fallback'}}}])
    assert len(processed4) == 1
Example #6
0
def test_polling():
    bot = VKBot(token='12345', group_id=12345)

    # test setting polling server
    assert bot._polling_server is None
    with patch('requests.get') as mock_get:
        mock_get.return_value.status_code = 200
        mock_get.return_value.json = lambda: {'response': {'server': 'abcd', 'key': 'xyz', 'ts': 23}}
        bot.set_polling_server()
        assert mock_get.called
        assert mock_get.call_args[1]['url'].endswith('groups.getLongPollServer')
        assert bot._polling_server == 'abcd'

    # test actually polling
    with patch('requests.get') as mock_get:
        mock_get.return_value.status_code = 200
        mock_get.return_value.json = lambda: {'updates': ['i am an update'], 'ts': 38}
        assert bot.retrieve_updates() == ['i am an update']
        assert mock_get.called
        assert mock_get.call_args[1]['url'] == 'abcd'
        assert mock_get.call_args[1]['params']['key'] == 'xyz'
        assert mock_get.call_args[1]['params']['ts'] == 23
        assert bot._polling_ts == 38
Example #7
0
    def __init__(
        self,
        connector: DialogConnector,
        telegram_token=None,
        facebook_access_token=None,
        facebook_verify_token=None,
        base_url=None,
        alice_url='alice/',
        telegram_url='tg/',
        facebook_url='fb/',
        vk_url='vk/',
        restart_webhook_url='restart_webhook',
        vk_token=None,
        vk_group_id=None,
        app=None,
    ):
        self.telegram_token = telegram_token or os.environ.get(
            'TOKEN') or os.environ.get('TELEGRAM_TOKEN')
        self.facebook_access_token = facebook_access_token or os.environ.get(
            'FACEBOOK_ACCESS_TOKEN')
        self.facebook_verify_token = facebook_verify_token or os.environ.get(
            'FACEBOOK_VERIFY_TOKEN')
        self.vk_token = vk_token or os.environ.get('VK_TOKEN')
        self.vk_group_id = vk_group_id or os.environ.get('VK_GROUP_ID')
        if base_url is None:
            base_url = os.environ.get('BASE_URL')
        self.base_url = base_url
        self.alice_url = alice_url
        self.telegram_url = telegram_url
        self.facebook_url = facebook_url
        self.vk_url = vk_url
        self.restart_webhook_url = restart_webhook_url

        self.connector = connector

        self.app = app or Flask(__name__)

        logger.info('The Alice webhook is available on "{}"'.format(
            self.alice_webhook_url))
        self.app.route(self.alice_webhook_url,
                       methods=['POST'])(self.alice_response)

        if self.telegram_token is not None:
            self.bot = telebot.TeleBot(self.telegram_token)
            self.bot.message_handler(func=lambda message: True)(
                self.tg_response)
            if base_url is not None:
                logger.info(
                    'Running Telegram bot with token "{}" on "{}"'.format(
                        self.telegram_token, self.telegram_webhook_url))
                self.app.route(self.telegram_webhook_url,
                               methods=['POST'])(self.get_tg_message)
                self.app.route("/" + self.restart_webhook_url)(
                    self.telegram_web_hook)
            else:
                logger.info(
                    'Running Telegram bot with token "{}", but without BASE_URL it can work only locally'
                    .format(self.telegram_token))
        else:
            logger.info(
                'Running no Telegram bot because TOKEN or BASE_URL was not provided'
            )
            self.bot = None

        if self.facebook_verify_token and self.facebook_access_token:
            logger.info('Running Facebook bot on "{}"'.format(
                self.facebook_webhook_url))
            self.app.route(self.facebook_webhook_url,
                           methods=['GET'
                                    ])(self.receive_fb_verification_request)
            self.app.route(self.facebook_webhook_url,
                           methods=['POST'])(self.facebook_response)
            self.facebook_bot = FacebookBot(self.facebook_access_token)
        else:
            logger.info(
                'Running no Facebook bot because FACEBOOK_ACCESS_TOKEN or FACEBOOK_VERIFY_TOKEN was not provided'
            )
            self.facebook_bot = None

        self.vk_bot = None
        if self.vk_token is None:
            logger.info('Skipping VK setup because vk_token is empty')
        elif self.vk_group_id is None:
            logger.info('Skipping VK setup because vk_group_id is empty')
        else:
            self.vk_bot = VKBot(token=self.vk_token, group_id=self.vk_group_id)
            self.vk_bot.message_handler()(self.vk_response)
            self.app.route("/" + self.vk_url, methods=[
                'POST'
            ])(lambda: self.vk_bot.process_webhook_data(request.json))

        self._processed_telegram_ids = set()
Example #8
0
class FlaskServer:
    def __init__(
        self,
        connector: DialogConnector,
        telegram_token=None,
        facebook_access_token=None,
        facebook_verify_token=None,
        base_url=None,
        alice_url='alice/',
        telegram_url='tg/',
        facebook_url='fb/',
        vk_url='vk/',
        restart_webhook_url='restart_webhook',
        vk_token=None,
        vk_group_id=None,
        app=None,
    ):
        self.telegram_token = telegram_token or os.environ.get(
            'TOKEN') or os.environ.get('TELEGRAM_TOKEN')
        self.facebook_access_token = facebook_access_token or os.environ.get(
            'FACEBOOK_ACCESS_TOKEN')
        self.facebook_verify_token = facebook_verify_token or os.environ.get(
            'FACEBOOK_VERIFY_TOKEN')
        self.vk_token = vk_token or os.environ.get('VK_TOKEN')
        self.vk_group_id = vk_group_id or os.environ.get('VK_GROUP_ID')
        if base_url is None:
            base_url = os.environ.get('BASE_URL')
        self.base_url = base_url
        self.alice_url = alice_url
        self.telegram_url = telegram_url
        self.facebook_url = facebook_url
        self.vk_url = vk_url
        self.restart_webhook_url = restart_webhook_url

        self.connector = connector

        self.app = app or Flask(__name__)

        logger.info('The Alice webhook is available on "{}"'.format(
            self.alice_webhook_url))
        self.app.route(self.alice_webhook_url,
                       methods=['POST'])(self.alice_response)

        if self.telegram_token is not None:
            self.bot = telebot.TeleBot(self.telegram_token)
            self.bot.message_handler(func=lambda message: True)(
                self.tg_response)
            if base_url is not None:
                logger.info(
                    'Running Telegram bot with token "{}" on "{}"'.format(
                        self.telegram_token, self.telegram_webhook_url))
                self.app.route(self.telegram_webhook_url,
                               methods=['POST'])(self.get_tg_message)
                self.app.route("/" + self.restart_webhook_url)(
                    self.telegram_web_hook)
            else:
                logger.info(
                    'Running Telegram bot with token "{}", but without BASE_URL it can work only locally'
                    .format(self.telegram_token))
        else:
            logger.info(
                'Running no Telegram bot because TOKEN or BASE_URL was not provided'
            )
            self.bot = None

        if self.facebook_verify_token and self.facebook_access_token:
            logger.info('Running Facebook bot on "{}"'.format(
                self.facebook_webhook_url))
            self.app.route(self.facebook_webhook_url,
                           methods=['GET'
                                    ])(self.receive_fb_verification_request)
            self.app.route(self.facebook_webhook_url,
                           methods=['POST'])(self.facebook_response)
            self.facebook_bot = FacebookBot(self.facebook_access_token)
        else:
            logger.info(
                'Running no Facebook bot because FACEBOOK_ACCESS_TOKEN or FACEBOOK_VERIFY_TOKEN was not provided'
            )
            self.facebook_bot = None

        self.vk_bot = None
        if self.vk_token is None:
            logger.info('Skipping VK setup because vk_token is empty')
        elif self.vk_group_id is None:
            logger.info('Skipping VK setup because vk_group_id is empty')
        else:
            self.vk_bot = VKBot(token=self.vk_token, group_id=self.vk_group_id)
            self.vk_bot.message_handler()(self.vk_response)
            self.app.route("/" + self.vk_url, methods=[
                'POST'
            ])(lambda: self.vk_bot.process_webhook_data(request.json))

        self._processed_telegram_ids = set()

    @property
    def alice_webhook_url(self):
        return "/" + self.alice_url

    @property
    def facebook_webhook_url(self):
        return '/' + self.facebook_url

    @property
    def telegram_webhook_url(self):
        return '/' + self.telegram_url + self.telegram_token

    def alice_response(self):
        logger.info('Got message from Alice: {}'.format(request.json))
        response = self.connector.respond(request.json, source=SOURCES.ALICE)
        logger.info('Sending message to Alice: {}'.format(response))
        return json.dumps(response, ensure_ascii=False, indent=2)

    def tg_response(self, message):
        logger.info('Got message from Telegram: {}'.format(message))
        if message.message_id in self._processed_telegram_ids:
            # avoid duplicate response after the bot starts
            logger.info(
                'Telegram message id {} is duplicate, skipping it'.format(
                    message.message_id))
            return
        self._processed_telegram_ids.add(message.message_id)
        # todo: cleanup old ids from _processed_telegram_ids
        response = self.connector.respond(message, source=SOURCES.TELEGRAM)
        telegram_response = self.bot.reply_to(message, **response)
        multimedia = response.pop('multimedia', [])
        for item in multimedia:
            if item['type'] == 'photo':
                self.bot.send_photo(message.chat.id,
                                    photo=item['content'],
                                    **response)
            if item['type'] == 'document':
                self.bot.send_document(message.chat.id,
                                       data=item['content'],
                                       **response)
            elif item['type'] == 'audio':
                self.bot.send_audio(message.chat.id,
                                    audio=item['content'],
                                    **response)
        logger.info('Sent a response to Telegram: {}'.format(message))

    def vk_response(self, message: VKMessage):
        response = self.connector.respond(message, source=SOURCES.VK)
        self.vk_bot.send_message(user_id=message.user_id, **response)

    def get_tg_message(self):
        self.bot.process_new_updates([
            telebot.types.Update.de_json(request.stream.read().decode("utf-8"))
        ])
        return "!", 200

    def telegram_web_hook(self):
        self.bot.remove_webhook()
        self.bot.set_webhook(url=self.base_url + self.telegram_url +
                             self.telegram_token)
        return "Telegram webhook restarted!", 200

    def vk_web_hook(self):
        endpoint = self.base_url + '/' + self.vk_url
        logger.info('Setting vk webhook on {}'.format(endpoint))
        self.vk_bot.set_postponed_webhook(url=endpoint, remove_old=True)

    def run_local_telegram(self):
        if self.bot is not None:
            self.bot.remove_webhook()
            self.bot.polling()
        else:
            raise ValueError(
                'Cannot run Telegram bot, because Telegram token was not found.'
            )

    def run_local_vk(self):
        if self.vk_bot is not None:
            self.vk_bot.remove_webhook()
            self.vk_bot.polling()
        else:
            raise ValueError('VK bot is not initialized and cannot run.')

    def receive_fb_verification_request(self):
        """Before allowing people to message your bot, Facebook has implemented a verify token
        that confirms all requests that your bot receives came from Facebook."""
        token_sent = request.args.get("hub.verify_token")
        if token_sent == self.facebook_verify_token:
            return request.args.get("hub.challenge")
        return 'Invalid verification token'

    def facebook_response(self):
        output = request.get_json()
        logger.info('Got messages from Facebook: {}'.format(output))
        for event in output['entry']:
            messaging = event['messaging']
            for message in messaging:
                if message.get('message') or message.get('postback'):
                    recipient_id = message['sender']['id']
                    response = self.connector.respond(message,
                                                      source=SOURCES.FACEBOOK)
                    logger.info(
                        'Sending message to Facebook: {}'.format(response))
                    self.facebook_bot.send_message(recipient_id, response)
        return "Message Processed"

    def run_server(self, host="0.0.0.0", port=None, use_ngrok=False):
        # todo: maybe, run a foreign app instead (attach own blueprint to it)
        if port is None:
            port = int(os.environ.get('PORT', 5000))
        if use_ngrok:
            from tgalice.server.flask_ngrok import run_ngrok
            logger.info('starting ngrok...')
            ngrok_address = run_ngrok(port)
            logger.info('ngrok_address is {}'.format(ngrok_address))
            self.base_url = ngrok_address
        if self.telegram_token is not None and self.base_url is not None:
            self.telegram_web_hook()
        else:
            warnings.warn(
                'Either telegram token or base_url was not found; cannot run Telegram bot.'
            )
        if self.vk_bot:
            self.vk_web_hook()
        else:
            logging.warning('VK bot has not been created; cannot run it')
        self.app.run(host=host, port=port)

    def run_command_line(self):
        input_sentence = ''
        while True:
            response, need_to_exit = self.connector.respond(
                input_sentence, source=SOURCES.TEXT)
            print(response)
            if need_to_exit:
                break
            input_sentence = input('> ')

    def parse_args_and_run(self):
        parser = argparse.ArgumentParser(description='Run the bot')
        parser.add_argument('--cli',
                            action='store_true',
                            help='Run the bot locally in command line mode')
        parser.add_argument(
            '--poll',
            action='store_true',
            help='Run the bot locally in polling mode (Telegram or VK)')
        parser.add_argument(
            '--ngrok',
            action='store_true',
            help='Run the bot locally with ngrok tunnel into the Internet')
        args = parser.parse_args()
        if args.cli:
            self.run_command_line()
        elif args.poll:
            if self.bot:
                self.run_local_telegram()
            elif self.vk_bot:
                self.run_local_vk()
            else:
                raise ValueError('Got no local bots to run in polling mode')
        else:
            self.run_server(use_ngrok=args.ngrok)
Example #9
0
import logging
import os

from tgalice.interfaces.vk import VKBot, VKMessage

logging.basicConfig(level=logging.DEBUG)

bot = VKBot(
    token=os.environ['VK_TOKEN'],
    group_id=os.environ['VK_GROUP_ID'],
    polling_wait=3,  # normally, timeout is about 20 seconds, but we make it shorter for quicker feedback
)


@bot.message_handler()
def respond(message: VKMessage):
    bot.send_message(
        user_id=message.user_id,
        text='Вы написали {}'.format(message.text),
        keyboard={
            'one_time': True,
            'buttons': [[{
                'action': {'type': 'text', 'label': 'окей'},
                'color': 'secondary',
            }]]
        },
    )


if __name__ == '__main__':
    bot.polling()
Example #10
0
import logging
import os

from flask import Flask, request

from tgalice.interfaces.vk import VKBot, VKMessage

logging.basicConfig(level=logging.INFO)
logging.getLogger('tgalice.interfaces.vk').setLevel(logging.DEBUG)

app = Flask(__name__)

bot = VKBot(
    token=os.environ['VK_TOKEN'],
    group_id=os.environ['VK_GROUP_ID'],
)


@bot.message_handler()
def respond(message: VKMessage):
    bot.send_message(
        user_id=message.user_id,
        text='Вы написали {}'.format(message.text),
        keyboard={'buttons': [[{
            'action': {
                'type': 'text',
                'label': 'ок'
            }
        }]]},
    )