We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Slack connector crashes after running dance several times
# Description
Slack connector crashes after running dance several times in a row

## Steps to Reproduce
Launch opsdroid with slack connector configured with bot token
go the channel connected in and run dance 3+ times in  a row

## Expected Functionality
Opsdroid keeps running fine


## Experienced Functionality
Opsdroid crashes with the following error:
```
ERROR slack.rtm.client: When calling '#process_message()' in the 'opsdroid.connector.slack' module the following error was raised: 'user'
Traceback (most recent call last):
  File "C:\Users\koe4945\desktop\git\personal\ops\venv\Scripts\opsdroid-script.py", line 11, in <module>
    load_entry_point('opsdroid', 'console_scripts', 'opsdroid')()
  File "c:\users\koe4945\desktop\git\personal\ops\venv\lib\site-packages\click\core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "c:\users\koe4945\desktop\git\personal\ops\venv\lib\site-packages\click\core.py", line 717, in main
    rv = self.invoke(ctx)
  File "c:\users\koe4945\desktop\git\personal\ops\venv\lib\site-packages\click\core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\users\koe4945\desktop\git\personal\ops\venv\lib\site-packages\click\core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\users\koe4945\desktop\git\personal\ops\venv\lib\site-packages\click\core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "c:\users\koe4945\desktop\git\personal\ops\opsdroid\opsdroid\cli\start.py", line 31, in start
    opsdroid.run()
  File "c:\users\koe4945\desktop\git\personal\ops\opsdroid\opsdroid\core.py", line 161, in run
    self.eventloop.run_until_complete(asyncio.gather(*pending))
  File "C:\Users\koe4945\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 584, in run_until_complete
    return future.result()
  File "c:\users\koe4945\desktop\git\personal\ops\python-slackclient\slack\rtm\client.py", line 339, in _connect_and_read
    await self._read_messages()
  File "c:\users\koe4945\desktop\git\personal\ops\python-slackclient\slack\rtm\client.py", line 390, in _read_messages
    await self._dispatch_event(event, data=payload)
  File "c:\users\koe4945\desktop\git\personal\ops\python-slackclient\slack\rtm\client.py", line 437, in _dispatch_event
    rtm_client=self, web_client=self._web_client, data=data
  File "c:\users\koe4945\desktop\git\personal\ops\opsdroid\opsdroid\connector\slack\__init__.py", line 104, in process_message
    user_info = await self.lookup_username(message["user"])
KeyError: 'user'
ERROR: Unhandled exception in opsdroid, exiting...
```

## Versions
- **Opsdroid version:**  v0.16.0+62.g620590f
- **Python version:**  3.7.3
- **OS/Docker version:**  Windows 10

## Configuration File
Please include your version of the configuration file below.

```yaml
connectors:
  - name: slack
    # required
    api-token: "[...bot token...]"

```

## Additional Details
Any other details you wish to include such as screenshots, console messages, etc.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `opsdroid/connector/slack/__init__.py`
Content:
```
1 """A connector for Slack."""
2 import logging
3 import re
4 
5 import slack
6 from emoji import demojize
7 
8 from opsdroid.connector import Connector, register_event
9 from opsdroid.events import Message, Reaction
10 from opsdroid.connector.slack.events import Blocks
11 
12 
13 _LOGGER = logging.getLogger(__name__)
14 
15 
16 class ConnectorSlack(Connector):
17     """A connector for Slack."""
18 
19     def __init__(self, config, opsdroid=None):
20         """Create the connector."""
21         super().__init__(config, opsdroid=opsdroid)
22         _LOGGER.debug(_("Starting Slack connector"))
23         self.name = "slack"
24         self.default_target = config.get("default-room", "#general")
25         self.icon_emoji = config.get("icon-emoji", ":robot_face:")
26         self.token = config["api-token"]
27         self.timeout = config.get("connect-timeout", 10)
28         self.slack = slack.WebClient(token=self.token, run_async=True)
29         self.slack_rtm = slack.RTMClient(token=self.token, run_async=True)
30         self.websocket = None
31         self.bot_name = config.get("bot-name", "opsdroid")
32         self.auth_info = None
33         self.user_info = None
34         self.bot_id = None
35         self.known_users = {}
36         self.keepalive = None
37         self.reconnecting = False
38         self.listening = True
39         self._message_id = 0
40 
41         # Register callbacks
42         slack.RTMClient.on(event="message", callback=self.process_message)
43 
44     async def connect(self):
45         """Connect to the chat service."""
46         _LOGGER.info(_("Connecting to Slack"))
47 
48         try:
49             # The slack library recommends you call `self.slack_rtm.start()`` here but it
50             # seems to mess with the event loop's signal handlers which breaks opsdroid.
51             # Therefore we need to directly call the private `_connect_and_read` method
52             # instead. This method also blocks so we need to dispatch it to the loop as a task.
53             self.opsdroid.eventloop.create_task(self.slack_rtm._connect_and_read())
54 
55             self.auth_info = (await self.slack.api_call("auth.test")).data
56             self.user_info = (
57                 await self.slack.api_call(
58                     "users.info",
59                     http_verb="GET",
60                     params={"user": self.auth_info["user_id"]},
61                 )
62             ).data
63             self.bot_id = self.user_info["user"]["profile"]["bot_id"]
64 
65             _LOGGER.debug(_("Connected as %s"), self.bot_name)
66             _LOGGER.debug(_("Using icon %s"), self.icon_emoji)
67             _LOGGER.debug(_("Default room is %s"), self.default_target)
68             _LOGGER.info(_("Connected successfully"))
69         except slack.errors.SlackApiError as error:
70             _LOGGER.error(
71                 _(
72                     "Unable to connect to Slack due to %s - "
73                     "The Slack Connector will not be available."
74                 ),
75                 error,
76             )
77         except Exception:
78             await self.disconnect()
79             raise
80 
81     async def disconnect(self):
82         """Disconnect from Slack."""
83         await self.slack_rtm.stop()
84         self.listening = False
85 
86     async def listen(self):
87         """Listen for and parse new messages."""
88 
89     async def process_message(self, **payload):
90         """Process a raw message and pass it to the parser."""
91         message = payload["data"]
92 
93         # Ignore own messages
94         if (
95             "subtype" in message
96             and message["subtype"] == "bot_message"
97             and message["bot_id"] == self.bot_id
98         ):
99             return
100 
101         # Lookup username
102         _LOGGER.debug(_("Looking up sender username"))
103         try:
104             user_info = await self.lookup_username(message["user"])
105         except ValueError:
106             return
107 
108         # Replace usernames in the message
109         _LOGGER.debug(_("Replacing userids in message with usernames"))
110         message["text"] = await self.replace_usernames(message["text"])
111 
112         await self.opsdroid.parse(
113             Message(
114                 message["text"],
115                 user_info["name"],
116                 message["channel"],
117                 self,
118                 raw_event=message,
119             )
120         )
121 
122     @register_event(Message)
123     async def send_message(self, message):
124         """Respond with a message."""
125         _LOGGER.debug(
126             _("Responding with: '%s' in room  %s"), message.text, message.target
127         )
128         await self.slack.api_call(
129             "chat.postMessage",
130             data={
131                 "channel": message.target,
132                 "text": message.text,
133                 "as_user": False,
134                 "username": self.bot_name,
135                 "icon_emoji": self.icon_emoji,
136             },
137         )
138 
139     @register_event(Blocks)
140     async def send_blocks(self, blocks):
141         """Respond with structured blocks."""
142         _LOGGER.debug(
143             _("Responding with interactive blocks in room  %s"), blocks.target
144         )
145         await self.slack.api_call(
146             "chat.postMessage",
147             data={
148                 "channel": blocks.target,
149                 "username": self.bot_name,
150                 "blocks": blocks.blocks,
151                 "icon_emoji": self.icon_emoji,
152             },
153         )
154 
155     @register_event(Reaction)
156     async def send_reaction(self, reaction):
157         """React to a message."""
158         emoji = demojize(reaction.emoji).replace(":", "")
159         _LOGGER.debug(_("Reacting with: %s"), emoji)
160         try:
161             await self.slack.api_call(
162                 "reactions.add",
163                 data={
164                     "name": emoji,
165                     "channel": reaction.target,
166                     "timestamp": reaction.linked_event.raw_event["ts"],
167                 },
168             )
169         except slack.errors.SlackApiError as error:
170             if "invalid_name" in str(error):
171                 _LOGGER.warning(_("Slack does not support the emoji %s"), emoji)
172             else:
173                 raise
174 
175     async def lookup_username(self, userid):
176         """Lookup a username and cache it."""
177         if userid in self.known_users:
178             user_info = self.known_users[userid]
179         else:
180             response = await self.slack.users_info(user=userid)
181             user_info = response.data["user"]
182             if isinstance(user_info, dict):
183                 self.known_users[userid] = user_info
184             else:
185                 raise ValueError("Returned user is not a dict.")
186         return user_info
187 
188     async def replace_usernames(self, message):
189         """Replace User ID with username in message text."""
190         userids = re.findall(r"\<\@([A-Z0-9]+)(?:\|.+)?\>", message)
191         for userid in userids:
192             user_info = await self.lookup_username(userid)
193             message = message.replace(
194                 "<@{userid}>".format(userid=userid), user_info["name"]
195             )
196         return message
197
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/opsdroid/connector/slack/__init__.py b/opsdroid/connector/slack/__init__.py
--- a/opsdroid/connector/slack/__init__.py
+++ b/opsdroid/connector/slack/__init__.py
@@ -90,6 +90,10 @@
         """Process a raw message and pass it to the parser."""
         message = payload["data"]
 
+        # Ignore message edits
+        if "subtype" in message and message["subtype"] == "message_changed":
+            return
+
         # Ignore own messages
         if (
             "subtype" in message

{"golden_diff": "diff --git a/opsdroid/connector/slack/__init__.py b/opsdroid/connector/slack/__init__.py\n--- a/opsdroid/connector/slack/__init__.py\n+++ b/opsdroid/connector/slack/__init__.py\n@@ -90,6 +90,10 @@\n         \"\"\"Process a raw message and pass it to the parser.\"\"\"\n         message = payload[\"data\"]\n \n+        # Ignore message edits\n+        if \"subtype\" in message and message[\"subtype\"] == \"message_changed\":\n+            return\n+\n         # Ignore own messages\n         if (\n             \"subtype\" in message\n", "issue": "Slack connector crashes after running dance several times\n# Description\r\nSlack connector crashes after running dance several times in a row\r\n\r\n## Steps to Reproduce\r\nLaunch opsdroid with slack connector configured with bot token\r\ngo the channel connected in and run dance 3+ times in  a row\r\n\r\n## Expected Functionality\r\nOpsdroid keeps running fine\r\n\r\n\r\n## Experienced Functionality\r\nOpsdroid crashes with the following error:\r\n```\r\nERROR slack.rtm.client: When calling '#process_message()' in the 'opsdroid.connector.slack' module the following error was raised: 'user'\r\nTraceback (most recent call last):\r\n  File \"C:\\Users\\koe4945\\desktop\\git\\personal\\ops\\venv\\Scripts\\opsdroid-script.py\", line 11, in <module>\r\n    load_entry_point('opsdroid', 'console_scripts', 'opsdroid')()\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\venv\\lib\\site-packages\\click\\core.py\", line 764, in __call__\r\n    return self.main(*args, **kwargs)\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\venv\\lib\\site-packages\\click\\core.py\", line 717, in main\r\n    rv = self.invoke(ctx)\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\venv\\lib\\site-packages\\click\\core.py\", line 1137, in invoke\r\n    return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\venv\\lib\\site-packages\\click\\core.py\", line 956, in invoke\r\n    return ctx.invoke(self.callback, **ctx.params)\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\venv\\lib\\site-packages\\click\\core.py\", line 555, in invoke\r\n    return callback(*args, **kwargs)\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\opsdroid\\opsdroid\\cli\\start.py\", line 31, in start\r\n    opsdroid.run()\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\opsdroid\\opsdroid\\core.py\", line 161, in run\r\n    self.eventloop.run_until_complete(asyncio.gather(*pending))\r\n  File \"C:\\Users\\koe4945\\AppData\\Local\\Programs\\Python\\Python37\\lib\\asyncio\\base_events.py\", line 584, in run_until_complete\r\n    return future.result()\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\python-slackclient\\slack\\rtm\\client.py\", line 339, in _connect_and_read\r\n    await self._read_messages()\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\python-slackclient\\slack\\rtm\\client.py\", line 390, in _read_messages\r\n    await self._dispatch_event(event, data=payload)\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\python-slackclient\\slack\\rtm\\client.py\", line 437, in _dispatch_event\r\n    rtm_client=self, web_client=self._web_client, data=data\r\n  File \"c:\\users\\koe4945\\desktop\\git\\personal\\ops\\opsdroid\\opsdroid\\connector\\slack\\__init__.py\", line 104, in process_message\r\n    user_info = await self.lookup_username(message[\"user\"])\r\nKeyError: 'user'\r\nERROR: Unhandled exception in opsdroid, exiting...\r\n```\r\n\r\n## Versions\r\n- **Opsdroid version:**  v0.16.0+62.g620590f\r\n- **Python version:**  3.7.3\r\n- **OS/Docker version:**  Windows 10\r\n\r\n## Configuration File\r\nPlease include your version of the configuration file below.\r\n\r\n```yaml\r\nconnectors:\r\n  - name: slack\r\n    # required\r\n    api-token: \"[...bot token...]\"\r\n\r\n```\r\n\r\n## Additional Details\r\nAny other details you wish to include such as screenshots, console messages, etc.\r\n\n", "before_files": [{"content": "\"\"\"A connector for Slack.\"\"\"\nimport logging\nimport re\n\nimport slack\nfrom emoji import demojize\n\nfrom opsdroid.connector import Connector, register_event\nfrom opsdroid.events import Message, Reaction\nfrom opsdroid.connector.slack.events import Blocks\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass ConnectorSlack(Connector):\n    \"\"\"A connector for Slack.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Create the connector.\"\"\"\n        super().__init__(config, opsdroid=opsdroid)\n        _LOGGER.debug(_(\"Starting Slack connector\"))\n        self.name = \"slack\"\n        self.default_target = config.get(\"default-room\", \"#general\")\n        self.icon_emoji = config.get(\"icon-emoji\", \":robot_face:\")\n        self.token = config[\"api-token\"]\n        self.timeout = config.get(\"connect-timeout\", 10)\n        self.slack = slack.WebClient(token=self.token, run_async=True)\n        self.slack_rtm = slack.RTMClient(token=self.token, run_async=True)\n        self.websocket = None\n        self.bot_name = config.get(\"bot-name\", \"opsdroid\")\n        self.auth_info = None\n        self.user_info = None\n        self.bot_id = None\n        self.known_users = {}\n        self.keepalive = None\n        self.reconnecting = False\n        self.listening = True\n        self._message_id = 0\n\n        # Register callbacks\n        slack.RTMClient.on(event=\"message\", callback=self.process_message)\n\n    async def connect(self):\n        \"\"\"Connect to the chat service.\"\"\"\n        _LOGGER.info(_(\"Connecting to Slack\"))\n\n        try:\n            # The slack library recommends you call `self.slack_rtm.start()`` here but it\n            # seems to mess with the event loop's signal handlers which breaks opsdroid.\n            # Therefore we need to directly call the private `_connect_and_read` method\n            # instead. This method also blocks so we need to dispatch it to the loop as a task.\n            self.opsdroid.eventloop.create_task(self.slack_rtm._connect_and_read())\n\n            self.auth_info = (await self.slack.api_call(\"auth.test\")).data\n            self.user_info = (\n                await self.slack.api_call(\n                    \"users.info\",\n                    http_verb=\"GET\",\n                    params={\"user\": self.auth_info[\"user_id\"]},\n                )\n            ).data\n            self.bot_id = self.user_info[\"user\"][\"profile\"][\"bot_id\"]\n\n            _LOGGER.debug(_(\"Connected as %s\"), self.bot_name)\n            _LOGGER.debug(_(\"Using icon %s\"), self.icon_emoji)\n            _LOGGER.debug(_(\"Default room is %s\"), self.default_target)\n            _LOGGER.info(_(\"Connected successfully\"))\n        except slack.errors.SlackApiError as error:\n            _LOGGER.error(\n                _(\n                    \"Unable to connect to Slack due to %s - \"\n                    \"The Slack Connector will not be available.\"\n                ),\n                error,\n            )\n        except Exception:\n            await self.disconnect()\n            raise\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Slack.\"\"\"\n        await self.slack_rtm.stop()\n        self.listening = False\n\n    async def listen(self):\n        \"\"\"Listen for and parse new messages.\"\"\"\n\n    async def process_message(self, **payload):\n        \"\"\"Process a raw message and pass it to the parser.\"\"\"\n        message = payload[\"data\"]\n\n        # Ignore own messages\n        if (\n            \"subtype\" in message\n            and message[\"subtype\"] == \"bot_message\"\n            and message[\"bot_id\"] == self.bot_id\n        ):\n            return\n\n        # Lookup username\n        _LOGGER.debug(_(\"Looking up sender username\"))\n        try:\n            user_info = await self.lookup_username(message[\"user\"])\n        except ValueError:\n            return\n\n        # Replace usernames in the message\n        _LOGGER.debug(_(\"Replacing userids in message with usernames\"))\n        message[\"text\"] = await self.replace_usernames(message[\"text\"])\n\n        await self.opsdroid.parse(\n            Message(\n                message[\"text\"],\n                user_info[\"name\"],\n                message[\"channel\"],\n                self,\n                raw_event=message,\n            )\n        )\n\n    @register_event(Message)\n    async def send_message(self, message):\n        \"\"\"Respond with a message.\"\"\"\n        _LOGGER.debug(\n            _(\"Responding with: '%s' in room  %s\"), message.text, message.target\n        )\n        await self.slack.api_call(\n            \"chat.postMessage\",\n            data={\n                \"channel\": message.target,\n                \"text\": message.text,\n                \"as_user\": False,\n                \"username\": self.bot_name,\n                \"icon_emoji\": self.icon_emoji,\n            },\n        )\n\n    @register_event(Blocks)\n    async def send_blocks(self, blocks):\n        \"\"\"Respond with structured blocks.\"\"\"\n        _LOGGER.debug(\n            _(\"Responding with interactive blocks in room  %s\"), blocks.target\n        )\n        await self.slack.api_call(\n            \"chat.postMessage\",\n            data={\n                \"channel\": blocks.target,\n                \"username\": self.bot_name,\n                \"blocks\": blocks.blocks,\n                \"icon_emoji\": self.icon_emoji,\n            },\n        )\n\n    @register_event(Reaction)\n    async def send_reaction(self, reaction):\n        \"\"\"React to a message.\"\"\"\n        emoji = demojize(reaction.emoji).replace(\":\", \"\")\n        _LOGGER.debug(_(\"Reacting with: %s\"), emoji)\n        try:\n            await self.slack.api_call(\n                \"reactions.add\",\n                data={\n                    \"name\": emoji,\n                    \"channel\": reaction.target,\n                    \"timestamp\": reaction.linked_event.raw_event[\"ts\"],\n                },\n            )\n        except slack.errors.SlackApiError as error:\n            if \"invalid_name\" in str(error):\n                _LOGGER.warning(_(\"Slack does not support the emoji %s\"), emoji)\n            else:\n                raise\n\n    async def lookup_username(self, userid):\n        \"\"\"Lookup a username and cache it.\"\"\"\n        if userid in self.known_users:\n            user_info = self.known_users[userid]\n        else:\n            response = await self.slack.users_info(user=userid)\n            user_info = response.data[\"user\"]\n            if isinstance(user_info, dict):\n                self.known_users[userid] = user_info\n            else:\n                raise ValueError(\"Returned user is not a dict.\")\n        return user_info\n\n    async def replace_usernames(self, message):\n        \"\"\"Replace User ID with username in message text.\"\"\"\n        userids = re.findall(r\"\\<\\@([A-Z0-9]+)(?:\\|.+)?\\>\", message)\n        for userid in userids:\n            user_info = await self.lookup_username(userid)\n            message = message.replace(\n                \"<@{userid}>\".format(userid=userid), user_info[\"name\"]\n            )\n        return message\n", "path": "opsdroid/connector/slack/__init__.py"}], "after_files": [{"content": "\"\"\"A connector for Slack.\"\"\"\nimport logging\nimport re\n\nimport slack\nfrom emoji import demojize\n\nfrom opsdroid.connector import Connector, register_event\nfrom opsdroid.events import Message, Reaction\nfrom opsdroid.connector.slack.events import Blocks\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass ConnectorSlack(Connector):\n    \"\"\"A connector for Slack.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Create the connector.\"\"\"\n        super().__init__(config, opsdroid=opsdroid)\n        _LOGGER.debug(_(\"Starting Slack connector\"))\n        self.name = \"slack\"\n        self.default_target = config.get(\"default-room\", \"#general\")\n        self.icon_emoji = config.get(\"icon-emoji\", \":robot_face:\")\n        self.token = config[\"api-token\"]\n        self.timeout = config.get(\"connect-timeout\", 10)\n        self.slack = slack.WebClient(token=self.token, run_async=True)\n        self.slack_rtm = slack.RTMClient(token=self.token, run_async=True)\n        self.websocket = None\n        self.bot_name = config.get(\"bot-name\", \"opsdroid\")\n        self.auth_info = None\n        self.user_info = None\n        self.bot_id = None\n        self.known_users = {}\n        self.keepalive = None\n        self.reconnecting = False\n        self.listening = True\n        self._message_id = 0\n\n        # Register callbacks\n        slack.RTMClient.on(event=\"message\", callback=self.process_message)\n\n    async def connect(self):\n        \"\"\"Connect to the chat service.\"\"\"\n        _LOGGER.info(_(\"Connecting to Slack\"))\n\n        try:\n            # The slack library recommends you call `self.slack_rtm.start()`` here but it\n            # seems to mess with the event loop's signal handlers which breaks opsdroid.\n            # Therefore we need to directly call the private `_connect_and_read` method\n            # instead. This method also blocks so we need to dispatch it to the loop as a task.\n            self.opsdroid.eventloop.create_task(self.slack_rtm._connect_and_read())\n\n            self.auth_info = (await self.slack.api_call(\"auth.test\")).data\n            self.user_info = (\n                await self.slack.api_call(\n                    \"users.info\",\n                    http_verb=\"GET\",\n                    params={\"user\": self.auth_info[\"user_id\"]},\n                )\n            ).data\n            self.bot_id = self.user_info[\"user\"][\"profile\"][\"bot_id\"]\n\n            _LOGGER.debug(_(\"Connected as %s\"), self.bot_name)\n            _LOGGER.debug(_(\"Using icon %s\"), self.icon_emoji)\n            _LOGGER.debug(_(\"Default room is %s\"), self.default_target)\n            _LOGGER.info(_(\"Connected successfully\"))\n        except slack.errors.SlackApiError as error:\n            _LOGGER.error(\n                _(\n                    \"Unable to connect to Slack due to %s - \"\n                    \"The Slack Connector will not be available.\"\n                ),\n                error,\n            )\n        except Exception:\n            await self.disconnect()\n            raise\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Slack.\"\"\"\n        await self.slack_rtm.stop()\n        self.listening = False\n\n    async def listen(self):\n        \"\"\"Listen for and parse new messages.\"\"\"\n\n    async def process_message(self, **payload):\n        \"\"\"Process a raw message and pass it to the parser.\"\"\"\n        message = payload[\"data\"]\n\n        # Ignore message edits\n        if \"subtype\" in message and message[\"subtype\"] == \"message_changed\":\n            return\n\n        # Ignore own messages\n        if (\n            \"subtype\" in message\n            and message[\"subtype\"] == \"bot_message\"\n            and message[\"bot_id\"] == self.bot_id\n        ):\n            return\n\n        # Lookup username\n        _LOGGER.debug(_(\"Looking up sender username\"))\n        try:\n            user_info = await self.lookup_username(message[\"user\"])\n        except ValueError:\n            return\n\n        # Replace usernames in the message\n        _LOGGER.debug(_(\"Replacing userids in message with usernames\"))\n        message[\"text\"] = await self.replace_usernames(message[\"text\"])\n\n        await self.opsdroid.parse(\n            Message(\n                message[\"text\"],\n                user_info[\"name\"],\n                message[\"channel\"],\n                self,\n                raw_event=message,\n            )\n        )\n\n    @register_event(Message)\n    async def send_message(self, message):\n        \"\"\"Respond with a message.\"\"\"\n        _LOGGER.debug(\n            _(\"Responding with: '%s' in room  %s\"), message.text, message.target\n        )\n        await self.slack.api_call(\n            \"chat.postMessage\",\n            data={\n                \"channel\": message.target,\n                \"text\": message.text,\n                \"as_user\": False,\n                \"username\": self.bot_name,\n                \"icon_emoji\": self.icon_emoji,\n            },\n        )\n\n    @register_event(Blocks)\n    async def send_blocks(self, blocks):\n        \"\"\"Respond with structured blocks.\"\"\"\n        _LOGGER.debug(\n            _(\"Responding with interactive blocks in room  %s\"), blocks.target\n        )\n        await self.slack.api_call(\n            \"chat.postMessage\",\n            data={\n                \"channel\": blocks.target,\n                \"username\": self.bot_name,\n                \"blocks\": blocks.blocks,\n                \"icon_emoji\": self.icon_emoji,\n            },\n        )\n\n    @register_event(Reaction)\n    async def send_reaction(self, reaction):\n        \"\"\"React to a message.\"\"\"\n        emoji = demojize(reaction.emoji).replace(\":\", \"\")\n        _LOGGER.debug(_(\"Reacting with: %s\"), emoji)\n        try:\n            await self.slack.api_call(\n                \"reactions.add\",\n                data={\n                    \"name\": emoji,\n                    \"channel\": reaction.target,\n                    \"timestamp\": reaction.linked_event.raw_event[\"ts\"],\n                },\n            )\n        except slack.errors.SlackApiError as error:\n            if \"invalid_name\" in str(error):\n                _LOGGER.warning(_(\"Slack does not support the emoji %s\"), emoji)\n            else:\n                raise\n\n    async def lookup_username(self, userid):\n        \"\"\"Lookup a username and cache it.\"\"\"\n        if userid in self.known_users:\n            user_info = self.known_users[userid]\n        else:\n            response = await self.slack.users_info(user=userid)\n            user_info = response.data[\"user\"]\n            if isinstance(user_info, dict):\n                self.known_users[userid] = user_info\n            else:\n                raise ValueError(\"Returned user is not a dict.\")\n        return user_info\n\n    async def replace_usernames(self, message):\n        \"\"\"Replace User ID with username in message text.\"\"\"\n        userids = re.findall(r\"\\<\\@([A-Z0-9]+)(?:\\|.+)?\\>\", message)\n        for userid in userids:\n            user_info = await self.lookup_username(userid)\n            message = message.replace(\n                \"<@{userid}>\".format(userid=userid), user_info[\"name\"]\n            )\n        return message\n", "path": "opsdroid/connector/slack/__init__.py"}]}