mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	Embedded bots: Add support for creating embedded bots via the API.
Adds support to add "Embedded bot" Service objects. This service handles every embedded bot. Extracted from "Embedded bots: Add support to add embedded bots from UI" by Robert Honig. Tweaked by tabbott to be disabled by default.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							ce4ba9c178
						
					
				
				
					commit
					a88178afaf
				
			@@ -515,6 +515,7 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin):
 | 
				
			|||||||
        DEFAULT_BOT,
 | 
					        DEFAULT_BOT,
 | 
				
			||||||
        INCOMING_WEBHOOK_BOT,
 | 
					        INCOMING_WEBHOOK_BOT,
 | 
				
			||||||
        OUTGOING_WEBHOOK_BOT,
 | 
					        OUTGOING_WEBHOOK_BOT,
 | 
				
			||||||
 | 
					        EMBEDDED_BOT,
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SERVICE_BOT_TYPES = [
 | 
					    SERVICE_BOT_TYPES = [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1001,3 +1001,43 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 | 
				
			|||||||
        bot_info['interface_type'] = Service.GENERIC
 | 
					        bot_info['interface_type'] = Service.GENERIC
 | 
				
			||||||
        result = self.client_post("/json/bots", bot_info)
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
        self.assert_json_success(result)
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_embedded_bot(self, **extras):
 | 
				
			||||||
 | 
					        # type: (**Any) -> None
 | 
				
			||||||
 | 
					        self.login(self.example_email('hamlet'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test to create embedded bot with correct service_name
 | 
				
			||||||
 | 
					        bot_info = {
 | 
				
			||||||
 | 
					            'full_name': 'Embedded test bot',
 | 
				
			||||||
 | 
					            'short_name': 'embeddedservicebot',
 | 
				
			||||||
 | 
					            'bot_type': UserProfile.EMBEDDED_BOT,
 | 
				
			||||||
 | 
					            'service_name': 'converter',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        bot_info.update(extras)
 | 
				
			||||||
 | 
					        with self.settings(EMBEDDED_BOTS_ENABLED=False):
 | 
				
			||||||
 | 
					            result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
 | 
					            self.assert_json_error(result, 'Embedded bots are not enabled.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bot_email = "embeddedservicebot-bot@zulip.testserver"
 | 
				
			||||||
 | 
					        bot_realm = get_realm('zulip')
 | 
				
			||||||
 | 
					        bot = get_user(bot_email, bot_realm)
 | 
				
			||||||
 | 
					        services = get_bot_services(bot.id)
 | 
				
			||||||
 | 
					        service = services[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(services), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(service.name, "converter")
 | 
				
			||||||
 | 
					        self.assertEqual(service.user_profile, bot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test to create embedded bot with incorrect service_name
 | 
				
			||||||
 | 
					        bot_info = {
 | 
				
			||||||
 | 
					            'full_name': 'Embedded test bot',
 | 
				
			||||||
 | 
					            'short_name': 'embeddedservicebot',
 | 
				
			||||||
 | 
					            'bot_type': UserProfile.EMBEDDED_BOT,
 | 
				
			||||||
 | 
					            'service_name': 'not_existing_service',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        bot_info.update(extras)
 | 
				
			||||||
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'Invalid embedded bot name.')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ from zerver.lib.actions import do_change_avatar_fields, do_change_bot_owner, \
 | 
				
			|||||||
    do_change_default_events_register_stream, do_change_default_sending_stream, \
 | 
					    do_change_default_events_register_stream, do_change_default_sending_stream, \
 | 
				
			||||||
    do_create_user, do_deactivate_user, do_reactivate_user, do_regenerate_api_key
 | 
					    do_create_user, do_deactivate_user, do_reactivate_user, do_regenerate_api_key
 | 
				
			||||||
from zerver.lib.avatar import avatar_url, get_gravatar_url, get_avatar_field
 | 
					from zerver.lib.avatar import avatar_url, get_gravatar_url, get_avatar_field
 | 
				
			||||||
 | 
					from zerver.lib.integrations import EMBEDDED_BOTS
 | 
				
			||||||
from zerver.lib.response import json_error, json_success
 | 
					from zerver.lib.response import json_error, json_success
 | 
				
			||||||
from zerver.lib.streams import access_stream_by_name
 | 
					from zerver.lib.streams import access_stream_by_name
 | 
				
			||||||
from zerver.lib.upload import upload_avatar_image
 | 
					from zerver.lib.upload import upload_avatar_image
 | 
				
			||||||
@@ -233,6 +234,7 @@ def regenerate_bot_api_key(request, user_profile, email):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    return json_success(json_result)
 | 
					    return json_success(json_result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Adds an outgoing webhook or embedded bot service.
 | 
				
			||||||
def add_service(name, user_profile, base_url=None, interface=None, token=None):
 | 
					def add_service(name, user_profile, base_url=None, interface=None, token=None):
 | 
				
			||||||
    # type: (Text, UserProfile, Text, int, Text) -> None
 | 
					    # type: (Text, UserProfile, Text, int, Text) -> None
 | 
				
			||||||
    Service.objects.create(name=name,
 | 
					    Service.objects.create(name=name,
 | 
				
			||||||
@@ -244,18 +246,26 @@ def add_service(name, user_profile, base_url=None, interface=None, token=None):
 | 
				
			|||||||
@has_request_variables
 | 
					@has_request_variables
 | 
				
			||||||
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name_raw=REQ("short_name"),
 | 
					def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name_raw=REQ("short_name"),
 | 
				
			||||||
                    bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
 | 
					                    bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
 | 
				
			||||||
                    payload_url=REQ(validator=check_url, default=None),
 | 
					                    payload_url=REQ(validator=check_url, default=""),
 | 
				
			||||||
 | 
					                    service_name=REQ(default=None),
 | 
				
			||||||
                    interface_type=REQ(validator=check_int, default=Service.GENERIC),
 | 
					                    interface_type=REQ(validator=check_int, default=Service.GENERIC),
 | 
				
			||||||
                    default_sending_stream_name=REQ('default_sending_stream', default=None),
 | 
					                    default_sending_stream_name=REQ('default_sending_stream', default=None),
 | 
				
			||||||
                    default_events_register_stream_name=REQ('default_events_register_stream', default=None),
 | 
					                    default_events_register_stream_name=REQ('default_events_register_stream', default=None),
 | 
				
			||||||
                    default_all_public_streams=REQ(validator=check_bool, default=None)):
 | 
					                    default_all_public_streams=REQ(validator=check_bool, default=None)):
 | 
				
			||||||
    # type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse
 | 
					    # type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], Optional[Text], int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse
 | 
				
			||||||
    short_name = check_short_name(short_name_raw)
 | 
					    short_name = check_short_name(short_name_raw)
 | 
				
			||||||
    service_name = short_name
 | 
					    service_name = service_name or short_name
 | 
				
			||||||
    short_name += "-bot"
 | 
					    short_name += "-bot"
 | 
				
			||||||
    full_name = check_full_name(full_name_raw)
 | 
					    full_name = check_full_name(full_name_raw)
 | 
				
			||||||
    email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
 | 
					    email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
 | 
				
			||||||
    form = CreateUserForm({'full_name': full_name, 'email': email})
 | 
					    form = CreateUserForm({'full_name': full_name, 'email': email})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if bot_type == UserProfile.EMBEDDED_BOT:
 | 
				
			||||||
 | 
					        if not settings.EMBEDDED_BOTS_ENABLED:
 | 
				
			||||||
 | 
					            return json_error(_("Embedded bots are not enabled."))
 | 
				
			||||||
 | 
					        if service_name not in [bot.name for bot in EMBEDDED_BOTS]:
 | 
				
			||||||
 | 
					            return json_error(_("Invalid embedded bot name."))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not form.is_valid():
 | 
					    if not form.is_valid():
 | 
				
			||||||
        # We validate client-side as well
 | 
					        # We validate client-side as well
 | 
				
			||||||
        return json_error(_('Bad name or username'))
 | 
					        return json_error(_('Bad name or username'))
 | 
				
			||||||
@@ -297,7 +307,7 @@ def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short
 | 
				
			|||||||
        user_file = list(request.FILES.values())[0]
 | 
					        user_file = list(request.FILES.values())[0]
 | 
				
			||||||
        upload_avatar_image(user_file, user_profile, bot_profile)
 | 
					        upload_avatar_image(user_file, user_profile, bot_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if bot_type == UserProfile.OUTGOING_WEBHOOK_BOT:
 | 
					    if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT):
 | 
				
			||||||
        add_service(name=service_name,
 | 
					        add_service(name=service_name,
 | 
				
			||||||
                    user_profile=bot_profile,
 | 
					                    user_profile=bot_profile,
 | 
				
			||||||
                    base_url=payload_url,
 | 
					                    base_url=payload_url,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,8 @@ EXTRA_INSTALLED_APPS = ["zilencer", "analytics"]
 | 
				
			|||||||
CAMO_URI = ''
 | 
					CAMO_URI = ''
 | 
				
			||||||
OPEN_REALM_CREATION = True
 | 
					OPEN_REALM_CREATION = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EMBEDDED_BOTS_ENABLED = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SAVE_FRONTEND_STACKTRACES = True
 | 
					SAVE_FRONTEND_STACKTRACES = True
 | 
				
			||||||
EVENT_LOGS_ENABLED = True
 | 
					EVENT_LOGS_ENABLED = True
 | 
				
			||||||
SYSTEM_ONLY_REALMS = set()  # type: Set[str]
 | 
					SYSTEM_ONLY_REALMS = set()  # type: Set[str]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -183,6 +183,7 @@ DEFAULT_SETTINGS = {
 | 
				
			|||||||
    'PUSH_NOTIFICATION_REDACT_CONTENT': False,
 | 
					    'PUSH_NOTIFICATION_REDACT_CONTENT': False,
 | 
				
			||||||
    'RATE_LIMITING': True,
 | 
					    'RATE_LIMITING': True,
 | 
				
			||||||
    'SEND_LOGIN_EMAILS': True,
 | 
					    'SEND_LOGIN_EMAILS': True,
 | 
				
			||||||
 | 
					    'EMBEDDED_BOTS_ENABLED': False,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# These settings are not documented in prod_settings_template.py.
 | 
					# These settings are not documented in prod_settings_template.py.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user