From 61d5d410675b686be9fa3ac4a0172246b2d772da Mon Sep 17 00:00:00 2001 From: Tomasz Kolek Date: Tue, 31 Jan 2017 09:17:05 +0100 Subject: [PATCH] Add Slack importer bot. --- api/integrations/slack/zulip_slack.py | 127 ++++++++++++++++++ api/integrations/slack/zulip_slack_config.py | 34 +++++ .../import-users-and-channels-from-slack.md | 44 ++++++ templates/zerver/help/index.md | 1 + 4 files changed, 206 insertions(+) create mode 100755 api/integrations/slack/zulip_slack.py create mode 100644 api/integrations/slack/zulip_slack_config.py create mode 100644 templates/zerver/help/import-users-and-channels-from-slack.md diff --git a/api/integrations/slack/zulip_slack.py b/api/integrations/slack/zulip_slack.py new file mode 100755 index 0000000000..6759781a08 --- /dev/null +++ b/api/integrations/slack/zulip_slack.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# +# slacker is a dependency for this script. +# + +from __future__ import absolute_import +from __future__ import print_function + +import sys +import string +import random +from six.moves import range +from typing import List, Dict + +import zulip +from slacker import Slacker, Response, Error as SlackError + +import zulip_slack_config as config + + +client = zulip.Client(email=config.ZULIP_USER, api_key=config.ZULIP_API_KEY, site=config.ZULIP_SITE) + + +class FromSlackImporter(object): + def __init__(self, slack_token, get_archived_channels=True): + # type: (str, bool) -> None + self.slack = Slacker(slack_token) + self.get_archived_channels = get_archived_channels + + self._check_slack_token() + + def get_slack_users_email(self): + # type: () -> Dict[str, Dict[str, str]] + + r = self.slack.users.list() + self._check_if_response_is_successful(r) + results_dict = {} + for user in r.body['members']: + if user['profile'].get('email') and user.get('deleted') is False: + results_dict[user['id']] = {'email': user['profile']['email'], 'name': user['profile']['real_name']} + return results_dict + + def get_slack_public_channels_names(self): + # type: () -> List[Dict[str, str]] + + r = self.slack.channels.list() + self._check_if_response_is_successful(r) + return [{'name': channel['name'], 'members': channel['members']} for channel in r.body['channels']] + + def get_slack_private_channels_names(self): + # type: () -> List[str] + + r = self.slack.groups.list() + self._check_if_response_is_successful(r) + return [ + channel['name'] for channel in r.body['groups'] + if not channel['is_archived'] or self.get_archived_channels + ] + + def _check_slack_token(self): + # type: () -> None + try: + r = self.slack.api.test() + self._check_if_response_is_successful(r) + except SlackError as e: + print(e) + sys.exit(1) + except Exception as e: + print(e) + sys.exit(1) + + def _check_if_response_is_successful(self, response): + # type: (Response) -> None + print(response) + if not response.successful: + print(response.error) + sys.exit(1) + +def _generate_random_password(size=10): + # type: (int) -> str + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(size)) + +def get_and_add_users(slack_importer): + # type: (Slacker) -> Dict[str, Dict[str, str]] + users = slack_importer.get_slack_users_email() + added_users = {} + print('######### IMPORTING USERS STARTED #########\n') + for user_id, user in users.items(): + r = client.create_user({ + 'email': user['email'], + 'full_name': user['name'], + 'short_name': user['name'] + }) + if not r.get('msg'): + added_users[user_id] = user + print(u"{} -> {}\nCreated\n".format(user['name'], user['email'])) + else: + print(u"{} -> {}\n{}\n".format(user['name'], user['email'], r.get('msg'))) + print('######### IMPORTING USERS FINISHED #########\n') + return added_users + +def create_streams_and_add_subscribers(slack_importer, added_users): + # type: (Slacker, Dict[str, Dict[str, str]]) -> None + channels_list = slack_importer.get_slack_public_channels_names() + print('######### IMPORTING STREAMS STARTED #########\n') + for stream in channels_list: + subscribed_users = [added_users[member]['email'] for member in stream['members'] if member in added_users.keys()] + if subscribed_users: + r = client.add_subscriptions([{"name": stream['name']}], principals=subscribed_users) + if not r.get('msg'): + print(u"{} -> created\n".format(stream['name'])) + else: + print(u"{} -> {}\n".format(stream['name'], r.get('msg'))) + else: + print(u"{} -> wasn't created\nNo subscribers\n".format(stream['name'])) + print('######### IMPORTING STREAMS FINISHED #########\n') + +def main(): + # type: () -> None + importer = FromSlackImporter(config.SLACK_TOKEN) + added_users = get_and_add_users(importer) + create_streams_and_add_subscribers(importer, added_users) + +if __name__ == '__main__': + main() diff --git a/api/integrations/slack/zulip_slack_config.py b/api/integrations/slack/zulip_slack_config.py new file mode 100644 index 0000000000..dbed3d19a3 --- /dev/null +++ b/api/integrations/slack/zulip_slack_config.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Copyright © 2014 Zulip, Inc. +# +# 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. + + +### REQUIRED CONFIGURATION ### + +# Change these values to your Slack credentials. +SLACK_TOKEN = 'slack_token' + +# Change these values to the credentials for your Slack bot. +ZULIP_USER = 'user-email@zulip.com' +ZULIP_API_KEY = 'user-email_api_key' + +# Set this to your Zulip API server URI +ZULIP_SITE = 'https://zulip.example.com' diff --git a/templates/zerver/help/import-users-and-channels-from-slack.md b/templates/zerver/help/import-users-and-channels-from-slack.md new file mode 100644 index 0000000000..b8281e52de --- /dev/null +++ b/templates/zerver/help/import-users-and-channels-from-slack.md @@ -0,0 +1,44 @@ +# Import users and channels from Slack + +{!follow-steps.md!} import users and channels from Slack to Zulip. + +!!! warn "" + **Note:** Please ensure that you have admin rights before importing users and channels from Slack. + +1. Generate a Slack API token using Slack's [test token generator](https://api.slack.com/docs/oauth-test-tokens) + to import all of the necessary data. + +{!go-to-the.md!} [Your bots](/#settings/your-bots) +{!settings.md!} + +3. Click on the **Show/change your API key** button. + +4. Upon clicking the **Show/change your API key** button, + you will be asked to confirm your identity by entering + your password in the **Current password** field. + +5. Click the **Get API Key** button and copy the generated API Key. + +6. Fill all of the settings in `api/integrations/slack/zulip_slack_config.py`: + + * `SLACK_TOKEN` - the token from point number 1. + + * `ZULIP_USER` - the e-mail of the user (the user that API key was generated for). + + * `ZULIP_KEY` - the API key from point number 4. + + * `ZULIP_SITE` - the Zulip API server URI. + +7. Install the `slacker` dependency using the command `pip install slacker` + +8. Finally, run the script in your local Zulip directory using the command +`python api/integrations/slack/zulip_slack.py` + +## Importing users from a different organization + +If the users are not from the same organization, you should change your organization settings accordingly. + +{!go-to-the.md!} [Organization settings](/#administration/organization-settings) +{!admin.md!} + +2. Disable the **New users restricted to the following domains** option. diff --git a/templates/zerver/help/index.md b/templates/zerver/help/index.md index 53a59ca54a..76ef7b9766 100644 --- a/templates/zerver/help/index.md +++ b/templates/zerver/help/index.md @@ -106,6 +106,7 @@ as an **organization**. ## Tools & Customization * [Keyboard shortcuts](/help/keyboard-shortcuts) * [Add a bot or integration](/help/add-a-bot-or-integration) +* [Import users and channels from Slack](/help/import-users-and-channels-from-slack) ## Misc * [Tips for Zulip on Windows](/help/zulip-on-windows)