We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Handle slacker connector invalid token exception
<!-- Before you post an issue or if you are unsure about something join our gitter channel https://gitter.im/opsdroid/ and ask away! We are more than happy to help you. -->
# Description
This issue was found by @petri with #763

When we use an invalid token with the slacker connector opsdroid crashes badly. This is probably due to the fact that we changed dependencies and the code doesn't handle the exception raised by the aioslacker library.

We should probably refactor the connect method to check if the exception `slacker.Error: invalid_auth` was raised, if so an error message should be logged and opsdroid should still be able to run.


## Steps to Reproduce
- Set a slack connector with a bad token on config.yaml
- run opsdroid


## Expected Functionality
Opsdroid should still run but a message should be logged that the connector won't be active due to bad token.


## Experienced Functionality
Opsdroid crashes with a traceback

```python
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 206, in <module>
    init()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 203, in init
    main()
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 196, in main
    opsdroid.load()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py", line 153, in load
    self.start_connectors(self.modules["connectors"])
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py", line 248, in start_connectors
    self.eventloop.run_until_complete(connector.connect(self))
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 466, in run_until_complete
    return future.result()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/connector/slack/__init__.py", line 50, in connect
    connection = await self.slacker.rtm.start()
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/aioslacker/__init__.py", line 97, in __request
    raise Error(response.error)
slacker.Error: invalid_auth
Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 511, in __del__
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py", line 65, in close
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py", line 146, in remove_signal_handler
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/signal.py", line 47, in signal
TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object
```

## Versions
- **Opsdroid version:** latest
- **Python version:** 3.6
- **OS/Docker version:** MacOs Mojave

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

```yaml
connectors:
  - name: slack
     api-token: "jdit-ksd12okr"
```

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


<!-- Love opsdroid? Please consider supporting our collective:
 +👉  https://opencollective.com/opsdroid/donate -->

--- 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 asyncio
4 import json
5 import re
6 
7 import aiohttp
8 import websockets
9 import slacker
10 from aioslacker import Slacker
11 from emoji import demojize
12 
13 from opsdroid.connector import Connector
14 from opsdroid.events import Message
15 
16 
17 _LOGGER = logging.getLogger(__name__)
18 
19 
20 class ConnectorSlack(Connector):
21     """A connector for Slack."""
22 
23     def __init__(self, config, opsdroid=None):
24         """Create the connector."""
25         super().__init__(config, opsdroid=opsdroid)
26         _LOGGER.debug("Starting Slack connector")
27         self.name = "slack"
28         self.config = config
29         self.default_room = config.get("default-room", "#general")
30         self.icon_emoji = config.get("icon-emoji", ':robot_face:')
31         self.token = config["api-token"]
32         self.slacker = Slacker(self.token)
33         self.websocket = None
34         self.bot_name = config.get("bot-name", 'opsdroid')
35         self.known_users = {}
36         self.keepalive = None
37         self.reconnecting = False
38         self.listening = True
39         self._message_id = 0
40 
41     async def connect(self):
42         """Connect to the chat service."""
43         _LOGGER.info("Connecting to Slack")
44 
45         try:
46             connection = await self.slacker.rtm.start()
47             self.websocket = await websockets.connect(connection.body['url'])
48 
49             _LOGGER.debug("Connected as %s", self.bot_name)
50             _LOGGER.debug("Using icon %s", self.icon_emoji)
51             _LOGGER.debug("Default room is %s", self.default_room)
52             _LOGGER.info("Connected successfully")
53 
54             if self.keepalive is None or self.keepalive.done():
55                 self.keepalive = self.opsdroid.eventloop.create_task(
56                     self.keepalive_websocket())
57         except aiohttp.ClientOSError as error:
58             _LOGGER.error(error)
59             _LOGGER.error("Failed to connect to Slack, retrying in 10")
60             await self.reconnect(10)
61         except Exception:
62             await self.disconnect()
63             raise
64 
65     async def reconnect(self, delay=None):
66         """Reconnect to the websocket."""
67         try:
68             self.reconnecting = True
69             if delay is not None:
70                 await asyncio.sleep(delay)
71             await self.connect()
72         finally:
73             self.reconnecting = False
74 
75     async def disconnect(self):
76         """Disconnect from Slack."""
77         await self.slacker.close()
78 
79     async def listen(self):
80         """Listen for and parse new messages."""
81         while self.listening:
82             await self.receive_from_websocket()
83 
84     async def receive_from_websocket(self):
85         """Get the next message from the websocket."""
86         try:
87             content = await self.websocket.recv()
88             await self.process_message(json.loads(content))
89         except websockets.exceptions.ConnectionClosed:
90             _LOGGER.info("Slack websocket closed, reconnecting...")
91             await self.reconnect(5)
92 
93     async def process_message(self, message):
94         """Process a raw message and pass it to the parser."""
95         if "type" in message and message["type"] == "message" and \
96                 "user" in message:
97 
98             # Ignore bot messages
99             if "subtype" in message and \
100                     message["subtype"] == "bot_message":
101                 return
102 
103             # Lookup username
104             _LOGGER.debug("Looking up sender username")
105             try:
106                 user_info = await self.lookup_username(message["user"])
107             except ValueError:
108                 return
109 
110             # Replace usernames in the message
111             _LOGGER.debug("Replacing userids in message with usernames")
112             message["text"] = await self.replace_usernames(
113                 message["text"])
114 
115             await self.opsdroid.parse(Message(user_info["name"],
116                                               message["channel"],
117                                               self,
118                                               message["text"],
119                                               raw_event=message))
120 
121     async def respond(self, message, room=None):
122         """Respond with a message."""
123         _LOGGER.debug("Responding with: '%s' in room  %s",
124                       message.text, message.room)
125         await self.slacker.chat.post_message(message.room,
126                                              message.text,
127                                              as_user=False,
128                                              username=self.bot_name,
129                                              icon_emoji=self.icon_emoji)
130 
131     async def react(self, message, emoji):
132         """React to a message."""
133         emoji = demojize(emoji)
134         _LOGGER.debug("Reacting with: %s", emoji)
135         try:
136             await self.slacker.reactions.post('reactions.add', data={
137                 'name': emoji,
138                 'channel': message.room,
139                 'timestamp': message.raw_event['ts']
140             })
141         except slacker.Error as error:
142             if str(error) == 'invalid_name':
143                 _LOGGER.warning('Slack does not support the emoji %s', emoji)
144             else:
145                 raise
146 
147     async def keepalive_websocket(self):
148         """Keep pinging the websocket to keep it alive."""
149         while self.listening:
150             await self.ping_websocket()
151 
152     async def ping_websocket(self):
153         """Ping the websocket."""
154         await asyncio.sleep(60)
155         self._message_id += 1
156         try:
157             await self.websocket.send(
158                 json.dumps({'id': self._message_id, 'type': 'ping'}))
159         except (websockets.exceptions.InvalidState,
160                 websockets.exceptions.ConnectionClosed,
161                 aiohttp.ClientOSError,
162                 TimeoutError):
163             _LOGGER.info("Slack websocket closed, reconnecting...")
164             if not self.reconnecting:
165                 await self.reconnect()
166 
167     async def lookup_username(self, userid):
168         """Lookup a username and cache it."""
169         if userid in self.known_users:
170             user_info = self.known_users[userid]
171         else:
172             response = await self.slacker.users.info(userid)
173             user_info = response.body["user"]
174             if isinstance(user_info, dict):
175                 self.known_users[userid] = user_info
176             else:
177                 raise ValueError("Returned user is not a dict.")
178         return user_info
179 
180     async def replace_usernames(self, message):
181         """Replace User ID with username in message text."""
182         userids = re.findall(r"\<\@([A-Z0-9]+)(?:\|.+)?\>", message)
183         for userid in userids:
184             user_info = await self.lookup_username(userid)
185             message = message.replace("<@{userid}>".format(userid=userid),
186                                       user_info["name"])
187         return message
188
```
--- 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
@@ -58,6 +58,9 @@
             _LOGGER.error(error)
             _LOGGER.error("Failed to connect to Slack, retrying in 10")
             await self.reconnect(10)
+        except slacker.Error as error:
+            _LOGGER.error("Unable to connect to Slack due to %s - "
+                          "The Slack Connector will not be available.", error)
         except Exception:
             await self.disconnect()
             raise
@@ -79,7 +82,10 @@
     async def listen(self):
         """Listen for and parse new messages."""
         while self.listening:
-            await self.receive_from_websocket()
+            try:
+                await self.receive_from_websocket()
+            except AttributeError:
+                break
 
     async def receive_from_websocket(self):
         """Get the next message from the websocket."""

{"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@@ -58,6 +58,9 @@\n             _LOGGER.error(error)\n             _LOGGER.error(\"Failed to connect to Slack, retrying in 10\")\n             await self.reconnect(10)\n+        except slacker.Error as error:\n+            _LOGGER.error(\"Unable to connect to Slack due to %s - \"\n+                          \"The Slack Connector will not be available.\", error)\n         except Exception:\n             await self.disconnect()\n             raise\n@@ -79,7 +82,10 @@\n     async def listen(self):\n         \"\"\"Listen for and parse new messages.\"\"\"\n         while self.listening:\n-            await self.receive_from_websocket()\n+            try:\n+                await self.receive_from_websocket()\n+            except AttributeError:\n+                break\n \n     async def receive_from_websocket(self):\n         \"\"\"Get the next message from the websocket.\"\"\"\n", "issue": "Handle slacker connector invalid token exception\n<!-- Before you post an issue or if you are unsure about something join our gitter channel https://gitter.im/opsdroid/ and ask away! We are more than happy to help you. -->\r\n# Description\r\nThis issue was found by @petri with #763\r\n\r\nWhen we use an invalid token with the slacker connector opsdroid crashes badly. This is probably due to the fact that we changed dependencies and the code doesn't handle the exception raised by the aioslacker library.\r\n\r\nWe should probably refactor the connect method to check if the exception `slacker.Error: invalid_auth` was raised, if so an error message should be logged and opsdroid should still be able to run.\r\n\r\n\r\n## Steps to Reproduce\r\n- Set a slack connector with a bad token on config.yaml\r\n- run opsdroid\r\n\r\n\r\n## Expected Functionality\r\nOpsdroid should still run but a message should be logged that the connector won't be active due to bad token.\r\n\r\n\r\n## Experienced Functionality\r\nOpsdroid crashes with a traceback\r\n\r\n```python\r\nTraceback (most recent call last):\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py\", line 193, in _run_module_as_main\r\n    \"__main__\", mod_spec)\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py\", line 85, in _run_code\r\n    exec(code, run_globals)\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py\", line 206, in <module>\r\n    init()\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py\", line 203, in init\r\n    main()\r\n  File \"/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py\", line 764, in __call__\r\n    return self.main(*args, **kwargs)\r\n  File \"/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py\", line 717, in main\r\n    rv = self.invoke(ctx)\r\n  File \"/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py\", line 956, in invoke\r\n    return ctx.invoke(self.callback, **ctx.params)\r\n  File \"/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py\", line 555, in invoke\r\n    return callback(*args, **kwargs)\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py\", line 196, in main\r\n    opsdroid.load()\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py\", line 153, in load\r\n    self.start_connectors(self.modules[\"connectors\"])\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py\", line 248, in start_connectors\r\n    self.eventloop.run_until_complete(connector.connect(self))\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py\", line 466, in run_until_complete\r\n    return future.result()\r\n  File \"/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/connector/slack/__init__.py\", line 50, in connect\r\n    connection = await self.slacker.rtm.start()\r\n  File \"/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/aioslacker/__init__.py\", line 97, in __request\r\n    raise Error(response.error)\r\nslacker.Error: invalid_auth\r\nException ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>\r\nTraceback (most recent call last):\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py\", line 511, in __del__\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py\", line 65, in close\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py\", line 146, in remove_signal_handler\r\n  File \"/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/signal.py\", line 47, in signal\r\nTypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object\r\n```\r\n\r\n## Versions\r\n- **Opsdroid version:** latest\r\n- **Python version:** 3.6\r\n- **OS/Docker version:** MacOs Mojave\r\n\r\n## Configuration File\r\nPlease include your version of the configuration file bellow.\r\n\r\n```yaml\r\nconnectors:\r\n  - name: slack\r\n     api-token: \"jdit-ksd12okr\"\r\n```\r\n\r\n## Additional Details\r\nAny other details you wish to include such as screenshots, console messages, etc.\r\n\r\n\r\n<!-- Love opsdroid? Please consider supporting our collective:\r\n +\ud83d\udc49  https://opencollective.com/opsdroid/donate -->\n", "before_files": [{"content": "\"\"\"A connector for Slack.\"\"\"\nimport logging\nimport asyncio\nimport json\nimport re\n\nimport aiohttp\nimport websockets\nimport slacker\nfrom aioslacker import Slacker\nfrom emoji import demojize\n\nfrom opsdroid.connector import Connector\nfrom opsdroid.events import Message\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.config = config\n        self.default_room = config.get(\"default-room\", \"#general\")\n        self.icon_emoji = config.get(\"icon-emoji\", ':robot_face:')\n        self.token = config[\"api-token\"]\n        self.slacker = Slacker(self.token)\n        self.websocket = None\n        self.bot_name = config.get(\"bot-name\", 'opsdroid')\n        self.known_users = {}\n        self.keepalive = None\n        self.reconnecting = False\n        self.listening = True\n        self._message_id = 0\n\n    async def connect(self):\n        \"\"\"Connect to the chat service.\"\"\"\n        _LOGGER.info(\"Connecting to Slack\")\n\n        try:\n            connection = await self.slacker.rtm.start()\n            self.websocket = await websockets.connect(connection.body['url'])\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_room)\n            _LOGGER.info(\"Connected successfully\")\n\n            if self.keepalive is None or self.keepalive.done():\n                self.keepalive = self.opsdroid.eventloop.create_task(\n                    self.keepalive_websocket())\n        except aiohttp.ClientOSError as error:\n            _LOGGER.error(error)\n            _LOGGER.error(\"Failed to connect to Slack, retrying in 10\")\n            await self.reconnect(10)\n        except Exception:\n            await self.disconnect()\n            raise\n\n    async def reconnect(self, delay=None):\n        \"\"\"Reconnect to the websocket.\"\"\"\n        try:\n            self.reconnecting = True\n            if delay is not None:\n                await asyncio.sleep(delay)\n            await self.connect()\n        finally:\n            self.reconnecting = False\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Slack.\"\"\"\n        await self.slacker.close()\n\n    async def listen(self):\n        \"\"\"Listen for and parse new messages.\"\"\"\n        while self.listening:\n            await self.receive_from_websocket()\n\n    async def receive_from_websocket(self):\n        \"\"\"Get the next message from the websocket.\"\"\"\n        try:\n            content = await self.websocket.recv()\n            await self.process_message(json.loads(content))\n        except websockets.exceptions.ConnectionClosed:\n            _LOGGER.info(\"Slack websocket closed, reconnecting...\")\n            await self.reconnect(5)\n\n    async def process_message(self, message):\n        \"\"\"Process a raw message and pass it to the parser.\"\"\"\n        if \"type\" in message and message[\"type\"] == \"message\" and \\\n                \"user\" in message:\n\n            # Ignore bot messages\n            if \"subtype\" in message and \\\n                    message[\"subtype\"] == \"bot_message\":\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(\n                message[\"text\"])\n\n            await self.opsdroid.parse(Message(user_info[\"name\"],\n                                              message[\"channel\"],\n                                              self,\n                                              message[\"text\"],\n                                              raw_event=message))\n\n    async def respond(self, message, room=None):\n        \"\"\"Respond with a message.\"\"\"\n        _LOGGER.debug(\"Responding with: '%s' in room  %s\",\n                      message.text, message.room)\n        await self.slacker.chat.post_message(message.room,\n                                             message.text,\n                                             as_user=False,\n                                             username=self.bot_name,\n                                             icon_emoji=self.icon_emoji)\n\n    async def react(self, message, emoji):\n        \"\"\"React to a message.\"\"\"\n        emoji = demojize(emoji)\n        _LOGGER.debug(\"Reacting with: %s\", emoji)\n        try:\n            await self.slacker.reactions.post('reactions.add', data={\n                'name': emoji,\n                'channel': message.room,\n                'timestamp': message.raw_event['ts']\n            })\n        except slacker.Error as error:\n            if str(error) == 'invalid_name':\n                _LOGGER.warning('Slack does not support the emoji %s', emoji)\n            else:\n                raise\n\n    async def keepalive_websocket(self):\n        \"\"\"Keep pinging the websocket to keep it alive.\"\"\"\n        while self.listening:\n            await self.ping_websocket()\n\n    async def ping_websocket(self):\n        \"\"\"Ping the websocket.\"\"\"\n        await asyncio.sleep(60)\n        self._message_id += 1\n        try:\n            await self.websocket.send(\n                json.dumps({'id': self._message_id, 'type': 'ping'}))\n        except (websockets.exceptions.InvalidState,\n                websockets.exceptions.ConnectionClosed,\n                aiohttp.ClientOSError,\n                TimeoutError):\n            _LOGGER.info(\"Slack websocket closed, reconnecting...\")\n            if not self.reconnecting:\n                await self.reconnect()\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.slacker.users.info(userid)\n            user_info = response.body[\"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(\"<@{userid}>\".format(userid=userid),\n                                      user_info[\"name\"])\n        return message\n", "path": "opsdroid/connector/slack/__init__.py"}], "after_files": [{"content": "\"\"\"A connector for Slack.\"\"\"\nimport logging\nimport asyncio\nimport json\nimport re\n\nimport aiohttp\nimport websockets\nimport slacker\nfrom aioslacker import Slacker\nfrom emoji import demojize\n\nfrom opsdroid.connector import Connector\nfrom opsdroid.events import Message\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.config = config\n        self.default_room = config.get(\"default-room\", \"#general\")\n        self.icon_emoji = config.get(\"icon-emoji\", ':robot_face:')\n        self.token = config[\"api-token\"]\n        self.slacker = Slacker(self.token)\n        self.websocket = None\n        self.bot_name = config.get(\"bot-name\", 'opsdroid')\n        self.known_users = {}\n        self.keepalive = None\n        self.reconnecting = False\n        self.listening = True\n        self._message_id = 0\n\n    async def connect(self):\n        \"\"\"Connect to the chat service.\"\"\"\n        _LOGGER.info(\"Connecting to Slack\")\n\n        try:\n            connection = await self.slacker.rtm.start()\n            self.websocket = await websockets.connect(connection.body['url'])\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_room)\n            _LOGGER.info(\"Connected successfully\")\n\n            if self.keepalive is None or self.keepalive.done():\n                self.keepalive = self.opsdroid.eventloop.create_task(\n                    self.keepalive_websocket())\n        except aiohttp.ClientOSError as error:\n            _LOGGER.error(error)\n            _LOGGER.error(\"Failed to connect to Slack, retrying in 10\")\n            await self.reconnect(10)\n        except slacker.Error as error:\n            _LOGGER.error(\"Unable to connect to Slack due to %s - \"\n                          \"The Slack Connector will not be available.\", error)\n        except Exception:\n            await self.disconnect()\n            raise\n\n    async def reconnect(self, delay=None):\n        \"\"\"Reconnect to the websocket.\"\"\"\n        try:\n            self.reconnecting = True\n            if delay is not None:\n                await asyncio.sleep(delay)\n            await self.connect()\n        finally:\n            self.reconnecting = False\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Slack.\"\"\"\n        await self.slacker.close()\n\n    async def listen(self):\n        \"\"\"Listen for and parse new messages.\"\"\"\n        while self.listening:\n            try:\n                await self.receive_from_websocket()\n            except AttributeError:\n                break\n\n    async def receive_from_websocket(self):\n        \"\"\"Get the next message from the websocket.\"\"\"\n        try:\n            content = await self.websocket.recv()\n            await self.process_message(json.loads(content))\n        except websockets.exceptions.ConnectionClosed:\n            _LOGGER.info(\"Slack websocket closed, reconnecting...\")\n            await self.reconnect(5)\n\n    async def process_message(self, message):\n        \"\"\"Process a raw message and pass it to the parser.\"\"\"\n        if \"type\" in message and message[\"type\"] == \"message\" and \\\n                \"user\" in message:\n\n            # Ignore bot messages\n            if \"subtype\" in message and \\\n                    message[\"subtype\"] == \"bot_message\":\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(\n                message[\"text\"])\n\n            await self.opsdroid.parse(Message(user_info[\"name\"],\n                                              message[\"channel\"],\n                                              self,\n                                              message[\"text\"],\n                                              raw_event=message))\n\n    async def respond(self, message, room=None):\n        \"\"\"Respond with a message.\"\"\"\n        _LOGGER.debug(\"Responding with: '%s' in room  %s\",\n                      message.text, message.room)\n        await self.slacker.chat.post_message(message.room,\n                                             message.text,\n                                             as_user=False,\n                                             username=self.bot_name,\n                                             icon_emoji=self.icon_emoji)\n\n    async def react(self, message, emoji):\n        \"\"\"React to a message.\"\"\"\n        emoji = demojize(emoji)\n        _LOGGER.debug(\"Reacting with: %s\", emoji)\n        try:\n            await self.slacker.reactions.post('reactions.add', data={\n                'name': emoji,\n                'channel': message.room,\n                'timestamp': message.raw_event['ts']\n            })\n        except slacker.Error as error:\n            if str(error) == 'invalid_name':\n                _LOGGER.warning('Slack does not support the emoji %s', emoji)\n            else:\n                raise\n\n    async def keepalive_websocket(self):\n        \"\"\"Keep pinging the websocket to keep it alive.\"\"\"\n        while self.listening:\n            await self.ping_websocket()\n\n    async def ping_websocket(self):\n        \"\"\"Ping the websocket.\"\"\"\n        await asyncio.sleep(60)\n        self._message_id += 1\n        try:\n            await self.websocket.send(\n                json.dumps({'id': self._message_id, 'type': 'ping'}))\n        except (websockets.exceptions.InvalidState,\n                websockets.exceptions.ConnectionClosed,\n                aiohttp.ClientOSError,\n                TimeoutError):\n            _LOGGER.info(\"Slack websocket closed, reconnecting...\")\n            if not self.reconnecting:\n                await self.reconnect()\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.slacker.users.info(userid)\n            user_info = response.body[\"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(\"<@{userid}>\".format(userid=userid),\n                                      user_info[\"name\"])\n        return message\n", "path": "opsdroid/connector/slack/__init__.py"}]}