def start(self): """Initializes the bot, plugins, and everything.""" self.log.info(f"Starting SlackMinion version {self.version}") self.task_manager = AsyncTaskManager(self) self.bot_start_time = datetime.datetime.now() self.log.debug("Slack clients initialized.") self.webserver = Webserver(self.config["webserver"]["host"], self.config["webserver"]["port"]) self.plugin_manager.load() self.plugin_manager.load_state() self.rtm_client = MyRTMClient(token=self.config.get("slack_token"), run_async=True) self.api_client = AsyncWebClient(token=self.config.get("slack_token")) self.always_send_dm = ["_unauthorized_"] if "always_send_dm" in self.config: self.always_send_dm.extend( ["!" + x for x in self.config["always_send_dm"]]) self._add_event_handlers() self.is_setup = True if self.test_mode: self.metrics["startup_time"] = ( datetime.datetime.now() - self.bot_start_time).total_seconds() * 1000.0
def create_slack_client(config: SlackConversationConfiguration, run_async: bool = False): """Creates a Slack Web API client.""" if not run_async: return slack_sdk.WebClient( token=config.api_bot_token.get_secret_value()) return AsyncWebClient(token=config.api_bot_token.get_secret_value())
async def test_uninstallation_and_revokes(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) app._client = AsyncWebClient(token="uninstalled-revoked", base_url=self.mock_api_server_base_url) @app.event("app_uninstalled") async def handler1(say: AsyncSay): await say(channel="C111", text="What's up?") @app.event("tokens_revoked") async def handler2(say: AsyncSay): await say(channel="C111", text="What's up?") app_uninstalled_body = { "token": "verification_token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "type": "app_uninstalled" }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, } timestamp, body = str(int(time())), json.dumps(app_uninstalled_body) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 tokens_revoked_body = { "token": "verification_token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "type": "tokens_revoked", "tokens": { "oauth": ["UXXXXXXXX"], "bot": ["UXXXXXXXX"] }, }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, } timestamp, body = str(int(time())), json.dumps(tokens_revoked_body) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 # AsyncApp doesn't call auth.test when booting assert self.mock_received_requests.get("/auth.test") is None await asyncio.sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 2
async def test_missing_text_warnings_chat_update(self): client = AsyncWebClient(base_url="http://localhost:8888", token="xoxb-api_test") resp = await client.chat_update(channel="C111", ts="111.222", blocks=[]) self.assertIsNone(resp["error"])
async def run_exporter(): # Patch Slack API functions patch.patch() # Construct all needed instances of objects downloader = FileDownloader(settings.file_output_directory, settings.slack_token) slack_client = AsyncWebClient(token=settings.slack_token) fragment_factory = FragmentFactory() # Initialize context last_export_time = ExporterContext.get_last_export_time( settings.file_output_directory) ctx = ExporterContext(export_time=int(time.time()), last_export_time=last_export_time, output_directory=settings.file_output_directory, slack_client=slack_client, downloader=downloader, fragments=fragment_factory) # Run try: await exporter.export_all(ctx) except Exception as e: log.error(f"Uncaught {e.__class__.__name__}", exc_info=e) # Clean up await ctx.close()
def setUp(self): self.logger = logging.getLogger(__name__) self.org_admin_token = os.environ[ SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] self.sync_client: WebClient = WebClient(token=self.org_admin_token) self.async_client: AsyncWebClient = AsyncWebClient( token=self.org_admin_token) self.team_id = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID] self.idp_group_id = os.environ[SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID] if not hasattr(self, "channel_id"): team_admin_token = os.environ[ SLACK_SDK_TEST_GRID_WORKSPACE_ADMIN_USER_TOKEN] client = WebClient(token=team_admin_token) # Only fetching private channels since admin.conversations.restrictAccess methods do not work for public channels convs = client.conversations_list(exclude_archived=True, limit=100, types="private_channel") self.channel_id = next( (c["id"] for c in convs["channels"] if c["name"] != "general"), None) if self.channel_id is None: millis = int(round(time.time() * 1000)) channel_name = f"private-test-channel-{millis}" self.channel_id = client.conversations_create( name=channel_name, is_private=True, )["channel"]["id"]
def __init__(self, config, opsdroid=None): """Create the connector.""" super().__init__(config, opsdroid=opsdroid) _LOGGER.debug(_("Starting Slack connector.")) self.name = "slack" self.bot_token = config["bot-token"] self.bot_name = config.get("bot-name", "opsdroid") self.default_target = config.get("default-room", "#general") self.icon_emoji = config.get("icon-emoji", ":robot_face:") self.start_thread = config.get("start_thread", False) self.socket_mode = config.get("socket-mode", True) self.app_token = config.get("app-token") self.ssl_context = ssl.create_default_context(cafile=certifi.where()) self.slack_web_client = AsyncWebClient( token=self.bot_token, ssl=self.ssl_context, proxy=os.environ.get("HTTPS_PROXY"), ) self.socket_mode_client = (SocketModeClient( self.app_token, web_client=self.slack_web_client) if self.app_token else None) self.auth_info = None self.user_info = None self.bot_id = None self.known_users = {} self._event_creator = SlackEventCreator(self)
def __init__( self, app_token: str, logger: Optional[Logger] = None, web_client: Optional[AsyncWebClient] = None, auto_reconnect_enabled: bool = True, ping_interval: float = 10, ): """Socket Mode client Args: app_token: App-level token logger: Custom logger web_client: Web API client auto_reconnect_enabled: True if automatic reconnection is enabled (default: True) ping_interval: interval for ping-pong with Slack servers (seconds) """ self.app_token = app_token self.logger = logger or logging.getLogger(__name__) self.web_client = web_client or AsyncWebClient() self.closed = False self.default_auto_reconnect_enabled = auto_reconnect_enabled self.auto_reconnect_enabled = self.default_auto_reconnect_enabled self.ping_interval = ping_interval self.wss_uri = None self.message_queue = Queue() self.message_listeners = [] self.socket_mode_request_listeners = [] self.current_session = None self.current_session_monitor = None self.message_receiver = None self.message_processor = asyncio.ensure_future(self.process_messages())
def __init__( self, app_token: str, logger: Optional[Logger] = None, web_client: Optional[AsyncWebClient] = None, proxy: Optional[str] = None, auto_reconnect_enabled: bool = True, ping_interval: float = 10, on_message_listeners: Optional[List[Callable[[WSMessage], None]]] = None, on_error_listeners: Optional[List[Callable[[WSMessage], None]]] = None, on_close_listeners: Optional[List[Callable[[WSMessage], None]]] = None, ): self.app_token = app_token self.logger = logger or logging.getLogger(__name__) self.web_client = web_client or AsyncWebClient() self.closed = False self.proxy = proxy self.default_auto_reconnect_enabled = auto_reconnect_enabled self.auto_reconnect_enabled = self.default_auto_reconnect_enabled self.ping_interval = ping_interval self.wss_uri = None self.message_queue = Queue() self.message_listeners = [] self.socket_mode_request_listeners = [] self.current_session = None self.current_session_monitor = None self.on_message_listeners = on_message_listeners or [] self.on_error_listeners = on_error_listeners or [] self.on_close_listeners = on_close_listeners or [] self.message_receiver = None self.message_processor = asyncio.ensure_future(self.process_messages())
async def test_webhook(self): url = os.environ[SLACK_SDK_TEST_INCOMING_WEBHOOK_URL] webhook = AsyncWebhookClient(url) response = await webhook.send(text="Hello!") self.assertEqual(200, response.status_code) self.assertEqual("ok", response.body) token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] channel_name = os.environ[ SLACK_SDK_TEST_INCOMING_WEBHOOK_CHANNEL_NAME].replace("#", "") client = AsyncWebClient(token=token) channel_id = None async for resp in await client.conversations_list(limit=10): for c in resp["channels"]: if c["name"] == channel_name: channel_id = c["id"] break if channel_id is not None: break history = await client.conversations_history(channel=channel_id, limit=1) self.assertIsNotNone(history) actual_text = history["messages"][0]["text"] self.assertEqual("Hello!", actual_text)
class TestSocketModeWebsockets: valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" web_client = AsyncWebClient( token=valid_token, base_url=mock_api_server_base_url, ) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) @pytest.mark.asyncio async def test_events(self): t = Thread(target=start_socket_mode_server(self, 3022)) t.daemon = True t.start() await asyncio.sleep(1) # wait for the server app = AsyncApp(client=self.web_client) result = {"shortcut": False, "command": False} @app.shortcut("do-something") async def shortcut_handler(ack): result["shortcut"] = True await ack() @app.command("/hello-socket-mode") async def command_handler(ack): result["command"] = True await ack() handler = AsyncSocketModeHandler( app_token="xapp-A111-222-xyz", app=app, ) try: handler.client.wss_uri = "ws://localhost:3022/link" await handler.connect_async() await asyncio.sleep(2) # wait for the message receiver await handler.client.send_message("foo") await asyncio.sleep(2) assert result["shortcut"] is True assert result["command"] is True finally: await handler.client.close() self.server.stop() self.server.close()
async def test_backward_compatible_header_async(self): client: AsyncWebClient = AsyncWebClient(token=self.bot_token) try: while True: await client.users_list() except SlackApiError as e: self.assertIsNotNone(e.response.headers["Retry-After"])
def __init__( self, app_token: str, logger: Optional[Logger] = None, web_client: Optional[AsyncWebClient] = None, proxy: Optional[str] = None, auto_reconnect_enabled: bool = True, ping_interval: float = 10, on_message_listeners: Optional[List[Callable[[WSMessage], None]]] = None, on_error_listeners: Optional[List[Callable[[WSMessage], None]]] = None, on_close_listeners: Optional[List[Callable[[WSMessage], None]]] = None, ): """Socket Mode client Args: app_token: App-level token logger: Custom logger web_client: Web API client auto_reconnect_enabled: True if automatic reconnection is enabled (default: True) ping_interval: interval for ping-pong with Slack servers (seconds) proxy: the HTTP proxy URL on_message_listeners: listener functions for on_message on_error_listeners: listener functions for on_error on_close_listeners: listener functions for on_close """ self.app_token = app_token self.logger = logger or logging.getLogger(__name__) self.web_client = web_client or AsyncWebClient() self.closed = False self.proxy = proxy if self.proxy is None or len(self.proxy.strip()) == 0: env_variable = load_http_proxy_from_env(self.logger) if env_variable is not None: self.proxy = env_variable self.default_auto_reconnect_enabled = auto_reconnect_enabled self.auto_reconnect_enabled = self.default_auto_reconnect_enabled self.ping_interval = ping_interval self.wss_uri = None self.message_queue = Queue() self.message_listeners = [] self.socket_mode_request_listeners = [] self.current_session = None self.current_session_monitor = None # https://docs.aiohttp.org/en/stable/client_reference.html # Unless you are connecting to a large, unknown number of different servers # over the lifetime of your application, # it is suggested you use a single session for the lifetime of your application # to benefit from connection pooling. self.aiohttp_client_session = aiohttp.ClientSession() self.on_message_listeners = on_message_listeners or [] self.on_error_listeners = on_error_listeners or [] self.on_close_listeners = on_close_listeners or [] self.message_receiver = None self.message_processor = asyncio.ensure_future(self.process_messages())
def setUp(self): setup_mock_web_api_server(self) self.token_rotator = AsyncTokenRotator( client=AsyncWebClient(base_url="http://localhost:8888", token=None), client_id="111.222", client_secret="token_rotation_secret", )
def setUp(self): if not hasattr(self, "logger"): self.logger = logging.getLogger(__name__) self.bot_token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] self.async_client: AsyncWebClient = AsyncWebClient(token=self.bot_token) self.sync_client: WebClient = WebClient(token=self.bot_token) self.channel_id = os.environ[SLACK_SDK_TEST_WEB_TEST_CHANNEL_ID]
async def test_handle_callback(self): oauth_flow = AsyncOAuthFlow( client=AsyncWebClient(base_url=self.mock_api_server_base_url), settings=AsyncOAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), success_url="https://www.example.com/completion", failure_url="https://www.example.com/failure", ), ) state = await oauth_flow.issue_new_state(None) req = AsyncBoltRequest( body="", query=f"code=foo&state={state}", headers={ "cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"] }, ) resp = await oauth_flow.handle_callback(req) assert resp.status == 200 assert "https://www.example.com/completion" in resp.body app = AsyncApp(signing_secret="signing_secret", oauth_flow=oauth_flow) global_shortcut_body = { "type": "shortcut", "token": "verification_token", "action_ts": "111.111", "team": { "id": "T111", "domain": "workspace-domain", "enterprise_id": "E111", "enterprise_name": "Org Name", }, "user": { "id": "W111", "username": "******", "team_id": "T111" }, "callback_id": "test-shortcut", "trigger_id": "111.111.xxxxxx", } body = f"payload={quote(json.dumps(global_shortcut_body))}" timestamp = str(int(time())) signature_verifier = SignatureVerifier("signing_secret") headers = { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [ signature_verifier.generate_signature(body=body, timestamp=timestamp) ], "x-slack-request-timestamp": [timestamp], } request = AsyncBoltRequest(body=body, headers=headers) response = await app.async_dispatch(request) assert response.status == 200 await assert_auth_test_count_async(self, 1)
async def run_websocket_process(): from slack_sdk.socket_mode.aiohttp import SocketModeClient from slack_sdk.socket_mode.response import SocketModeResponse from slack_sdk.socket_mode.request import SocketModeRequest if not SLACK_SOCKET_MODE_APP_TOKEN: log.error("SLACK_SOCKET_MODE_APP_TOKEN not defined in .env file.") return # Initialize SocketModeClient with an app-level token + WebClient client = SocketModeClient( # This app-level token will be used only for establishing a connection app_token=str(SLACK_SOCKET_MODE_APP_TOKEN), # xapp-A111-222-xyz # You will be using this WebClient for performing Web API calls in listeners web_client=AsyncWebClient( token=str(SLACK_API_BOT_TOKEN)), # xoxb-111-222-xyz ) async def process(client: SocketModeClient, req: SocketModeRequest): db_session = SessionLocal() background_tasks = BackgroundTasks() if req.type == "events_api": response = await handle_slack_event( db_session=db_session, client=client.web_client, event=EventEnvelope(**req.payload), background_tasks=background_tasks, ) if req.type == "slash_commands": response = await handle_slack_command( db_session=db_session, client=client.web_client, request=req.payload, background_tasks=background_tasks, ) if req.type == "interactive": response = await handle_slack_action( db_session=db_session, client=client.web_client, request=req.payload, background_tasks=background_tasks, ) response = SocketModeResponse(envelope_id=req.envelope_id, payload=response) await client.send_socket_mode_response(response) # run the background tasks await background_tasks() # Add a new listener to receive messages from Slack # You can add more listeners like this client.socket_mode_request_listeners.append(process) # Establish a WebSocket connection to the Socket Mode servers await client.connect() await asyncio.sleep(float("inf"))
def setUp(self): self.logger = logging.getLogger(__name__) self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] self.team_id = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID] self.sync_client: WebClient = WebClient(token=self.org_admin_token) self.async_client: AsyncWebClient = AsyncWebClient(token=self.org_admin_token) self.channel_name = f"test-channel-{int(round(time.time() * 1000))}"
async def test_missing_text_warnings_chat_scheduleMessage(self): client = AsyncWebClient(base_url="http://localhost:8888", token="xoxb-api_test") resp = await client.chat_scheduleMessage(channel="C111", post_at="299876400", text="", blocks=[]) self.assertIsNone(resp["error"])
def setUp(self): self.logger = logging.getLogger(__name__) self.org_admin_token = os.environ[ SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] self.sync_client: WebClient = WebClient(token=self.org_admin_token) self.async_client: AsyncWebClient = AsyncWebClient( token=self.org_admin_token) self.user_ids = [os.environ[SLACK_SDK_TEST_GRID_USER_ID_ADMIN_AUTH]]
def create_async_web_client( token: Optional[str] = None, logger: Optional[Logger] = None ) -> AsyncWebClient: return AsyncWebClient( token=token, logger=logger, user_agent_prefix=f"Bolt-Async/{bolt_version}", )
class TestAsyncAppDispatch: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) @pytest.mark.asyncio async def test_none_body(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, ) req = AsyncBoltRequest(body=None, headers={}, mode="http") response = await app.async_dispatch(req) # request verification failure assert response.status == 401 assert response.body == '{"error": "invalid request"}' req = AsyncBoltRequest(body=None, headers={}, mode="socket_mode") response = await app.async_dispatch(req) # request verification is skipped for Socket Mode assert response.status == 404 assert response.body == '{"error": "unhandled request"}' @pytest.mark.asyncio async def test_none_body_no_middleware(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, ssl_check_enabled=False, ignoring_self_events_enabled=False, request_verification_enabled=False, # token_verification_enabled=False, url_verification_enabled=False, ) req = AsyncBoltRequest(body=None, headers={}, mode="http") response = await app.async_dispatch(req) assert response.status == 404 assert response.body == '{"error": "unhandled request"}' req = AsyncBoltRequest(body=None, headers={}, mode="socket_mode") response = await app.async_dispatch(req) assert response.status == 404 assert response.body == '{"error": "unhandled request"}'
async def test_user_agent_customization_issue_769_async(self): client = AsyncWebClient( base_url="http://localhost:8888", token="xoxb-user-agent this_is test", user_agent_prefix="this_is", user_agent_suffix="test", ) resp = await client.api_test() self.assertTrue(resp["ok"])
async def test_if_it_uses_custom_logger(self): logger = CustomLogger("test-logger") client = AsyncWebClient( base_url="http://localhost:8888", token="xoxb-api_test", logger=logger, ) await client.chat_postMessage(channel="C111", text="hello") self.assertTrue(logger.called)
async def test_html_response_body_issue_829_async(self): client = AsyncWebClient(base_url="http://localhost:8888") try: await client.users_list(token="xoxb-error_html_response") self.fail("SlackApiError expected here") except err.SlackApiError as e: self.assertEqual( "The request to the Slack API failed.\n" "The server responded with: {}", str(e))
async def test_bytes_for_file_param_async(self): client: AsyncWebClient = AsyncWebClient(token=self.bot_token) bytes = bytearray("This is a test", "utf-8") upload = await client.files_upload(file=bytes, filename="test.txt", channels=self.channel_ids) self.assertIsNotNone(upload) deletion = await client.files_delete(file=upload["file"]["id"]) self.assertIsNotNone(deletion)
def __init__( self, *, client_id: str, client_secret: str, client: Optional[AsyncWebClient] = None, ): self.client = client if client is not None else AsyncWebClient(token=None) self.client_id = client_id self.client_secret = client_secret
def __init__(self, chans, graphlast, filepath, imagepath): self.slack_config = yaml.safe_load(open("slack_config.yml")) self.slack_client = AsyncWebClient( token=self.slack_config['SLACK_BOT_TOKEN']) self.filepath = filepath self.imagepath = imagepath self.graphlast = graphlast self.chans = chans self.gs = False asyncio.run(self.init_messages())
def setUp(self): self.logger = logging.getLogger(__name__) self.org_admin_token = os.environ[ SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] self.sync_client: WebClient = WebClient(token=self.org_admin_token) self.sync_client.retry_handlers.append( RateLimitErrorRetryHandler(max_retry_count=2)) self.async_client: AsyncWebClient = AsyncWebClient( token=self.org_admin_token) self.async_client.retry_handlers.append( AsyncRateLimitErrorRetryHandler(max_retry_count=2))
async def test_failure_pattern(self): authorization = AsyncSingleTeamAuthorization() req = AsyncBoltRequest(body="payload={}", headers={}) req.context["client"] = AsyncWebClient( base_url=self.mock_api_server_base_url, token="dummy") resp = BoltResponse(status=404) resp = await authorization.async_process(req=req, resp=resp, next=next) assert resp.status == 200 assert resp.body == ":x: Please install this app into the workspace :bow:"