2017-10-20 17:24:09 +02:00
|
|
|
import json
|
2017-06-21 20:43:26 +02:00
|
|
|
import os
|
2017-07-21 17:54:34 +02:00
|
|
|
import importlib
|
2017-11-27 02:00:04 +01:00
|
|
|
from zerver.lib.actions import internal_send_private_message, \
|
2019-02-09 03:01:35 +01:00
|
|
|
internal_send_stream_message_by_name, internal_send_huddle_message
|
2018-05-21 05:04:16 +02:00
|
|
|
from zerver.models import UserProfile, get_active_user
|
2017-11-24 10:18:29 +01:00
|
|
|
from zerver.lib.bot_storage import get_bot_storage, set_bot_storage, \
|
2019-02-02 23:53:55 +01:00
|
|
|
is_key_in_bot_storage, remove_bot_storage
|
2018-01-07 19:17:25 +01:00
|
|
|
from zerver.lib.bot_config import get_bot_config, ConfigError
|
2017-07-21 17:54:34 +02:00
|
|
|
from zerver.lib.integrations import EMBEDDED_BOTS
|
2018-11-10 22:50:28 +01:00
|
|
|
from zerver.lib.topic import get_topic_from_message_info
|
2017-06-21 20:43:26 +02:00
|
|
|
|
2019-01-07 16:20:21 +01:00
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
2019-02-02 23:53:55 +01:00
|
|
|
from typing import Any, Dict
|
2017-06-21 20:43:26 +02:00
|
|
|
|
|
|
|
our_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
2017-07-17 19:30:48 +02:00
|
|
|
from zulip_bots.lib import RateLimit
|
2017-06-21 20:43:26 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def get_bot_handler(service_name: str) -> Any:
|
2017-07-21 17:54:34 +02:00
|
|
|
|
2017-07-25 19:03:09 +02:00
|
|
|
# Check that this service is present in EMBEDDED_BOTS, add exception handling.
|
2020-03-02 20:38:35 +01:00
|
|
|
configured_service = ""
|
|
|
|
for embedded_bot_service in EMBEDDED_BOTS:
|
|
|
|
if service_name == embedded_bot_service.name:
|
|
|
|
configured_service = embedded_bot_service.name
|
|
|
|
if not configured_service:
|
2017-07-25 19:03:09 +02:00
|
|
|
return None
|
2020-03-02 20:38:35 +01:00
|
|
|
bot_module_name = 'zulip_bots.bots.%s.%s' % (configured_service, configured_service)
|
2017-07-21 17:54:34 +02:00
|
|
|
bot_module = importlib.import_module(bot_module_name) # type: Any
|
|
|
|
return bot_module.handler_class()
|
|
|
|
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2017-11-05 11:37:41 +01:00
|
|
|
class StateHandler:
|
2017-11-24 10:18:29 +01:00
|
|
|
storage_size_limit = 10000000 # type: int # TODO: Store this in the server configuration model.
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, user_profile: UserProfile) -> None:
|
2017-10-12 16:34:05 +02:00
|
|
|
self.user_profile = user_profile
|
2017-10-20 17:24:09 +02:00
|
|
|
self.marshal = lambda obj: json.dumps(obj)
|
|
|
|
self.demarshal = lambda obj: json.loads(obj)
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2018-05-10 19:13:36 +02:00
|
|
|
def get(self, key: str) -> str:
|
2017-11-24 10:18:29 +01:00
|
|
|
return self.demarshal(get_bot_storage(self.user_profile, key))
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2018-05-10 19:13:36 +02:00
|
|
|
def put(self, key: str, value: str) -> None:
|
2017-11-24 10:18:29 +01:00
|
|
|
set_bot_storage(self.user_profile, [(key, self.marshal(value))])
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2018-05-10 19:13:36 +02:00
|
|
|
def remove(self, key: str) -> None:
|
2017-11-24 10:18:29 +01:00
|
|
|
remove_bot_storage(self.user_profile, [key])
|
2017-10-26 16:02:35 +02:00
|
|
|
|
2018-05-10 19:13:36 +02:00
|
|
|
def contains(self, key: str) -> bool:
|
2017-11-24 10:18:29 +01:00
|
|
|
return is_key_in_bot_storage(self.user_profile, key)
|
2017-10-12 16:34:05 +02:00
|
|
|
|
2018-02-08 15:51:38 +01:00
|
|
|
class EmbeddedBotQuitException(Exception):
|
|
|
|
pass
|
|
|
|
|
2019-01-07 16:20:21 +01:00
|
|
|
class EmbeddedBotEmptyRecipientsList(Exception):
|
|
|
|
pass
|
|
|
|
|
2017-11-05 11:37:41 +01:00
|
|
|
class EmbeddedBotHandler:
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, user_profile: UserProfile) -> None:
|
2017-06-21 20:43:26 +02:00
|
|
|
# Only expose a subset of our UserProfile's functionality
|
|
|
|
self.user_profile = user_profile
|
|
|
|
self._rate_limit = RateLimit(20, 5)
|
2017-07-17 19:30:48 +02:00
|
|
|
self.full_name = user_profile.full_name
|
|
|
|
self.email = user_profile.email
|
2017-10-20 17:42:57 +02:00
|
|
|
self.storage = StateHandler(user_profile)
|
2018-12-17 22:26:59 +01:00
|
|
|
self.user_id = user_profile.id
|
2017-06-21 20:43:26 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def send_message(self, message: Dict[str, Any]) -> None:
|
2017-11-27 01:53:28 +01:00
|
|
|
if not self._rate_limit.is_legal():
|
2017-06-21 20:43:26 +02:00
|
|
|
self._rate_limit.show_error_and_exit()
|
2017-11-27 02:00:04 +01:00
|
|
|
|
|
|
|
if message['type'] == 'stream':
|
2019-02-09 03:01:35 +01:00
|
|
|
internal_send_stream_message_by_name(
|
2019-02-08 03:13:14 +01:00
|
|
|
self.user_profile.realm, self.user_profile,
|
2019-02-09 03:01:35 +01:00
|
|
|
message['to'], message['topic'], message['content']
|
2019-02-08 03:13:14 +01:00
|
|
|
)
|
2017-11-27 02:00:04 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
assert message['type'] == 'private'
|
|
|
|
# Ensure that it's a comma-separated list, even though the
|
|
|
|
# usual 'to' field could be either a List[str] or a str.
|
|
|
|
recipients = ','.join(message['to']).split(',')
|
|
|
|
|
2019-01-07 16:20:21 +01:00
|
|
|
if len(message['to']) == 0:
|
|
|
|
raise EmbeddedBotEmptyRecipientsList(_('Message must have recipients!'))
|
|
|
|
elif len(message['to']) == 1:
|
2018-05-21 05:04:16 +02:00
|
|
|
recipient_user = get_active_user(recipients[0], self.user_profile.realm)
|
2017-11-27 02:00:04 +01:00
|
|
|
internal_send_private_message(self.user_profile.realm, self.user_profile,
|
|
|
|
recipient_user, message['content'])
|
|
|
|
else:
|
|
|
|
internal_send_huddle_message(self.user_profile.realm, self.user_profile,
|
|
|
|
recipients, message['content'])
|
2017-06-21 20:43:26 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def send_reply(self, message: Dict[str, Any], response: str) -> None:
|
2017-07-17 19:30:48 +02:00
|
|
|
if message['type'] == 'private':
|
|
|
|
self.send_message(dict(
|
|
|
|
type='private',
|
2017-10-10 14:29:59 +02:00
|
|
|
to=[x['email'] for x in message['display_recipient']],
|
2017-07-17 19:30:48 +02:00
|
|
|
content=response,
|
|
|
|
sender_email=message['sender_email'],
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
self.send_message(dict(
|
|
|
|
type='stream',
|
|
|
|
to=message['display_recipient'],
|
2018-11-10 22:50:28 +01:00
|
|
|
topic=get_topic_from_message_info(message),
|
2017-07-17 19:30:48 +02:00
|
|
|
content=response,
|
|
|
|
sender_email=message['sender_email'],
|
|
|
|
))
|
2017-06-21 20:43:26 +02:00
|
|
|
|
2018-01-07 19:17:25 +01:00
|
|
|
# The bot_name argument exists only to comply with ExternalBotHandler.get_config_info().
|
2018-05-10 19:13:36 +02:00
|
|
|
def get_config_info(self, bot_name: str, optional: bool=False) -> Dict[str, str]:
|
2018-01-07 19:17:25 +01:00
|
|
|
try:
|
|
|
|
return get_bot_config(self.user_profile)
|
|
|
|
except ConfigError:
|
|
|
|
if optional:
|
|
|
|
return dict()
|
|
|
|
raise
|
2018-02-08 15:51:38 +01:00
|
|
|
|
|
|
|
def quit(self, message: str= "") -> None:
|
|
|
|
raise EmbeddedBotQuitException(message)
|