Esempio n. 1
0
def get_channel_info(channel1, verbose=False):
    try:
        botToken = os.environ.get('BotUserOAuthAccessToken')
        slack_client = SlackClient(botToken)
        info = slack_client.conversations_info(channel=channel1)
        if info['ok']:
            if info['channel']['is_private']:
                if verbose:
                    return 'this is a *private channel*'
                else:
                    return ''
            else:
                return '\nWARNING: this is a *public channel*'
        else:
            return '\nWARNING: I can not check if this channel is private'
    except Exception as e:
        if verbose:
            return '\nWARNING. I can not check if this channel is private! ' + str(
                e)
        else:
            return '\nWARNING. I can not check if this channel is private! '
Esempio n. 2
0
class LowLevelSlackClient(metaclass=Singleton):
    def __init__(self):
        _settings, _ = import_settings()
        slack_api_token = _settings.get('SLACK_API_TOKEN', None)
        http_proxy = _settings.get('HTTP_PROXY', None)
        self.rtm_client = RTMClient(token=slack_api_token, proxy=http_proxy)
        self.web_client = WebClient(token=slack_api_token, proxy=http_proxy)
        self._bot_info = {}
        self._users = {}
        self._channels = {}

    @staticmethod
    def get_instance() -> 'LowLevelSlackClient':
        return LowLevelSlackClient()

    def _register_user(self, user_response):
        user = User.from_api_response(user_response)
        self._users[user.id] = user
        return user

    def _register_channel(self, channel_response):
        channel = Channel.from_api_response(channel_response)
        self._channels[channel.id] = channel
        return channel

    def ping(self):
        # Ugly hack because some parts of slackclient > 2.0 are async-only (like the ping function)
        # and Slack Machine isn't async yet
        loop = asyncio.new_event_loop()
        result = self.rtm_client.ping()
        loop.run_until_complete(result)

    def _on_open(self, **payload):
        # Set bot info
        self._bot_info = payload['data']['self']
        # Build user cache
        all_users = call_paginated_endpoint(self.web_client.users_list,
                                            'members')
        for u in all_users:
            self._register_user(u)
        logger.debug("Number of users found: %s" % len(self._users))
        logger.debug("Users: %s" % ", ".join([
            f"{u.profile.display_name}|{u.profile.real_name}"
            for u in self._users.values()
        ]))
        # Build channel cache
        all_channels = call_paginated_endpoint(
            self.web_client.conversations_list,
            'channels',
            types='public_channel,private_channel,mpim,im')
        for c in all_channels:
            self._register_channel(c)
        logger.debug("Number of channels found: %s" % len(self._channels))
        logger.debug("Channels: %s" %
                     ", ".join([c.identifier
                                for c in self._channels.values()]))

    def _on_team_join(self, **payload):
        user = self._register_user(payload['data']['user'])
        logger.debug("User joined team: %s" % user)

    def _on_user_change(self, **payload):
        user = self._register_user(payload['data']['user'])
        logger.debug("User changed: %s" % user)

    def _on_channel_created(self, **payload):
        channel_resp = self.web_client.conversations_info(
            channel=payload['data']['channel']['id'])
        channel = self._register_channel(channel_resp['channel'])
        logger.debug("Channel created: %s" % channel)

    def _on_channel_updated(self, **payload):
        data = payload['data']
        if isinstance(data['channel'], dict):
            channel_id = data['channel']['id']
        else:
            channel_id = data['channel']
        channel_resp = self.web_client.conversations_info(channel=channel_id)
        channel = self._register_channel(channel_resp['channel'])
        logger.debug("Channel updated: %s" % channel)

    def _on_channel_deleted(self, **payload):
        channel = self._channels[payload['data']['channel']]
        del self._channels[payload['data']['channel']]
        logger.debug("Channel %s deleted" % channel.name)

    @property
    def bot_info(self) -> Dict[str, str]:
        return self._bot_info

    def start(self):
        RTMClient.on(event='open', callback=self._on_open)
        RTMClient.on(event='team_join', callback=self._on_team_join)
        RTMClient.on(event='channel_created',
                     callback=self._on_channel_created)
        RTMClient.on(event='group_joined', callback=self._on_channel_created)
        RTMClient.on(event='mpim_joined', callback=self._on_channel_created)
        RTMClient.on(event='im_created', callback=self._on_channel_created)
        RTMClient.on(event='channel_deleted',
                     callback=self._on_channel_deleted)
        RTMClient.on(event='im_close', callback=self._on_channel_deleted)
        RTMClient.on(event='channel_rename', callback=self._on_channel_updated)
        RTMClient.on(event='channel_archive',
                     callback=self._on_channel_updated)
        RTMClient.on(event='channel_unarchive',
                     callback=self._on_channel_updated)
        RTMClient.on(event='user_change', callback=self._on_user_change)
        self.rtm_client.start()

    @property
    def users(self) -> Dict[str, User]:
        return self._users

    @property
    def channels(self) -> Dict[str, Channel]:
        return self._channels
Esempio n. 3
0
    def post(self, request, *args, **kwargs):
        """Handle an inbound HTTP POST request representing a user interaction with a UI element."""
        valid, reason = verify_signature(request)
        if not valid:
            return HttpResponse(status=401, reason=reason)

        payload = json.loads(request.POST.get("payload", ""))

        context = {
            "request_scheme": request.scheme,
            "request_host": request.get_host(),
            "org_id": payload.get("team", {}).get("id"),
            "org_name": payload.get("team", {}).get("domain"),
            "channel_id": payload.get("channel", {}).get("id"),
            "channel_name": payload.get("channel", {}).get("name"),
            "user_id": payload.get("user", {}).get("id"),
            "user_name": payload.get("user", {}).get("username"),
            "response_url": payload.get("response_url"),
            "trigger_id": payload.get("trigger_id"),
        }

        # Check for channel_name if channel_id is present
        if context["channel_name"] is None and context[
                "channel_id"] is not None:
            # Build a Slack Client Object
            slack_client = WebClient(
                token=settings.PLUGINS_CONFIG["nautobot_chatops"]
                ["slack_api_token"])

            # Get the channel information from Slack API
            channel_info = slack_client.conversations_info(
                channel=context["channel_id"])

            # Assign the Channel name out of the conversations info end point
            context["channel_name"] = channel_info["channel"]["name"]

        if "actions" in payload and payload["actions"]:
            # Block action triggered by a non-modal interactive component
            action = payload["actions"][0]
            action_id = action.get("action_id", "")
            block_id = action.get("block_id", "")
            if action["type"] == "static_select":
                value = action.get("selected_option", {}).get("value", "")
                selected_value = f"'{value}'"
            elif action["type"] == "button":
                value = action.get("value")
                selected_value = f"'{value}'"
            else:
                logger.error(f"Unhandled action type {action['type']}")
                return HttpResponse(status=500)

            if settings.PLUGINS_CONFIG["nautobot_chatops"].get(
                    "delete_input_on_submission"):
                # Delete the interactive element since it's served its purpose
                SlackDispatcher(context).delete_message(
                    context["response_url"])
            if action_id == "action" and selected_value == "cancel":
                # Nothing more to do
                return HttpResponse()
        elif "view" in payload and payload["view"]:
            # View submission triggered from a modal dialog
            logger.info("Submission triggered from a modal dialog")
            logger.info(json.dumps(payload, indent=2))
            values = payload["view"].get("state", {}).get("values", {})

            # Handling for multiple fields. This will be used when the multi_input_dialog() method of the Slack
            # Dispatcher class is utilized.
            if len(values) > 1:
                selected_value = ""
                callback_id = payload["view"].get("callback_id")
                # sometimes in the case of back-to-back dialogs there will be
                # parameters included in the callback_id.  Below parses those
                # out and adds them to selected_value.
                try:
                    cmds = shlex.split(callback_id)
                except ValueError as e:
                    logger.error("%s", e)
                    return HttpResponse(
                        f"Error: {e} encountered when processing {callback_id}"
                    )
                for i, cmd in enumerate(cmds):
                    if i == 2:
                        selected_value += f"'{cmd}'"
                    elif i > 2:
                        selected_value += f" '{cmd}'"
                action_id = f"{cmds[0]} {cmds[1]}"

                sorted_params = sorted(values.keys())
                for blk_id in sorted_params:
                    for act_id in values[blk_id].values():
                        if act_id["type"] == "static_select":
                            value = act_id["selected_option"]["value"]
                            selected_value += f" '{value}'"
                        elif act_id["type"] == "plain_text_input":
                            value = act_id["value"]
                            selected_value += f" '{value}'"
                        else:
                            logger.error(
                                f"Unhandled dialog type {act_id['type']}")
                            return HttpResponse(status=500)
            # Original un-modified single-field handling below
            else:
                block_id = sorted(values.keys())[0]
                action_id = sorted(values[block_id].keys())[0]
                action = values[block_id][action_id]
                if action["type"] == "plain_text_input":
                    value = action["value"]
                    selected_value = f"'{value}'"
                else:
                    logger.error(f"Unhandled action type {action['type']}")
                    return HttpResponse(status=500)

            # Modal view submissions don't generally contain a channel ID, but we hide one for our convenience:
            if "private_metadata" in payload["view"]:
                private_metadata = json.loads(
                    payload["view"]["private_metadata"])
                if "channel_id" in private_metadata:
                    context["channel_id"] = private_metadata["channel_id"]
        else:
            return HttpResponse("I didn't understand that notification.")

        logger.info(
            f"action_id: {action_id}, selected_value: {selected_value}")
        try:
            command, subcommand, params = parse_command_string(
                f"{action_id} {selected_value}")
        except ValueError as e:
            logger.error("%s", e)
            # Tried sending 400 error, but the friendly message never made it to slack.
            return HttpResponse(
                f"'Error: {e}' encountered on command '{action_id} {selected_value}'."
            )
        logger.info(
            f"command: {command}, subcommand: {subcommand}, params: {params}")

        registry = get_commands_registry()

        if command not in registry:
            SlackDispatcher(context).send_markdown(
                commands_help(prefix=SLASH_PREFIX))
            return HttpResponse()

        # What we'd like to do here is send a "Nautobot is typing..." to the channel,
        # but unfortunately the API we're using doesn't support that (only the legacy/deprecated RTM API does).
        # SlackDispatcher(context).send_busy_indicator()

        return check_and_enqueue_command(registry, command, subcommand, params,
                                         context, SlackDispatcher)