Source code for slack.actions

import json
import typing
import logging
from typing import Any, Dict, Iterator, Optional
from collections import defaultdict
from collections.abc import MutableMapping

from . import exceptions

LOG = logging.getLogger(__name__)


[docs]class Action(MutableMapping): """ MutableMapping representing a response to an interactive message, a dialog submission or a message action. Args: raw_action: Decoded body of the HTTP request verification_token: Slack verification token used to verify the request came from slack team_id: Verify the event is for the correct team Raises: :class:`slack.exceptions.FailedVerification`: when `verification_token` or `team_id` does not match the incoming event's """ def __init__( self, raw_action: typing.MutableMapping, verification_token: Optional[str] = None, team_id: Optional[str] = None, ) -> None: self.action = raw_action if verification_token and self.action["token"] != verification_token: raise exceptions.FailedVerification( self.action["token"], self.action["team"]["id"] ) if team_id and self.action["team"]["id"] != team_id: raise exceptions.FailedVerification( self.action["token"], self.action["team"]["id"] ) def __getitem__(self, item): return self.action[item] def __setitem__(self, key, value): self.action[key] = value def __delitem__(self, key): del self.action[key] def __iter__(self): return iter(self.action) def __len__(self): return len(self.action) def __repr__(self): return str(self.action) @classmethod def from_http( cls, payload: typing.MutableMapping, verification_token: Optional[str] = None, team_id: Optional[str] = None, ) -> "Action": action = json.loads(payload["payload"]) return cls(action, verification_token=verification_token, team_id=team_id)
[docs]class Router: """ When creating a slack applications you can only set one action url. This provide a routing mechanism for the incoming actions, based on their `callback_id` and the action name, to one or more handlers. """ def __init__(self): self._routes: Dict[str, Dict] = defaultdict(dict)
[docs] def register(self, callback_id: str, handler: Any, name: str = "*") -> None: """ Register a new handler for a specific :class:`slack.actions.Action` `callback_id`. Optional routing based on the action name too. The name argument is useful for actions of type `interactive_message` to provide a different handler for each individual action. Args: callback_id: Callback_id the handler is interested in handler: Callback name: Name of the action (optional). """ LOG.info("Registering %s, %s to %s", callback_id, name, handler) if name not in self._routes[callback_id]: self._routes[callback_id][name] = [] self._routes[callback_id][name].append(handler)
[docs] def register_interactive_message( self, callback_id: str, handler: Any, name: str = "*" ) -> None: """ Register a new handler for a specific :class:`slack.actions.Action` `callback_id`. Optional routing based on the action name too. The name argument is useful for actions of type `interactive_message` to provide a different handler for each individual action. Internally calls the base :meth:`register<slack.actions.Router.register>` method for actual registration. Args: callback_id: Callback_id the handler is interested in handler: Callback name: Name of the action (optional). """ self.register(callback_id, handler, name)
[docs] def register_block_action( self, block_id: str, handler: Any, action_id: str = "*" ) -> None: """ Register a new handler for a block-based :class:`slack.actions.Action`. Internally uses the base `register` method for actual registration. Optionally provides routing based on a specific `action_id` if present. Args: block_id: The action block_id the handler is interested in handler: Callback action_id: specific action_id for the action (optional) """ self.register(block_id, handler, action_id)
[docs] def register_dialog_submission(self, callback_id: str, handler: Any): """ Registers a new handler for a `dialog_submission` :class:`slack.actions.Action`. Internally calls the base :meth:`register<slack.actions.Router.register>` method for actual registration. Args: callback_id: Callback_id the handler is interested in handler: Callback """ self.register(callback_id, handler)
[docs] def dispatch(self, action: Action) -> Any: """ Yields handlers matching the incoming :class:`slack.actions.Action` `callback_id` or `action_id`. Args: action: :class:`slack.actions.Action` Yields: handler """ if "callback_id" in action: LOG.debug( "Dispatching action %s, %s", action["type"], action["callback_id"] ) else: LOG.debug( "Dispatching action %s, %s", action["actions"][0]["type"], action["actions"][0]["action_id"], ) if action["type"] == "interactive_message": yield from self._dispatch_interactive_message(action) elif action["type"] in ("dialog_submission", "message_action"): yield from self._dispatch_action(action) elif action["type"] == "block_actions": yield from self._dispatch_block_actions(action) else: raise UnknownActionType(action)
def _dispatch_action(self, action: Action) -> Iterator[Any]: yield from self._routes[action["callback_id"]].get("*", []) def _dispatch_interactive_message(self, action: Action) -> Iterator[Any]: if action["actions"][0]["name"] in self._routes[action["callback_id"]]: yield from self._routes[action["callback_id"]][action["actions"][0]["name"]] else: yield from self._routes[action["callback_id"]].get("*", []) def _dispatch_block_actions(self, action: Action) -> Iterator[Any]: block_id = action["actions"][0]["block_id"] action_id = action["actions"][0].get("action_id", "*") if action_id in self._routes[block_id]: yield from self._routes[block_id].get(action_id, []) else: yield from self._routes[block_id].get("*", [])
[docs]class UnknownActionType(Exception): """ Raised for incoming action with unknown type Attributes: action: The incoming action """ def __init__(self, action: Action) -> None: self.action = action