Repository: opsdroid/opsdroid

Issue:
Slack connector should check if message event exists before iterating
When sending a lot of messages to a slack block (for example sending output from the terminal to a block) we will get a rate limit and get reset by peer.

The `_send_message` method will freeze opsdroid and we have to kill it from the terminal

```python
ERROR opsdroid.core: Exception when running skill 'nuke'.
Traceback (most recent call last):
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 969, in _wrap_create_connection
    return await self._loop.create_connection(*args, **kwargs)  # type: ignore  # noqa
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/base_events.py", line 1050, in create_connection
    transport, protocol = await self._create_connection_transport(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/base_events.py", line 1080, in _create_connection_transport
    await waiter
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/selector_events.py", line 846, in _read_ready__data_received
    data = self._sock.recv(self.max_size)
ConnectionResetError: [Errno 54] Connection reset by peer
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 465, in run_skill
    return await skill(event)
  File "/Users/samanthahughes/Library/Application Support/opsdroid/opsdroid-modules/skill/nuke/__init__.py", line 454, in block
    await self.handle_nuke_arm(account=account)
  File "/Users/samanthahughes/Library/Application Support/opsdroid/opsdroid-modules/skill/nuke/__init__.py", line 361, in handle_nuke_arm
    await self.opsdroid.send(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 662, in send
    return await event.connector.send(event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/__init__.py", line 220, in send
    return await self.events[type(event)](self, event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/connector.py", line 300, in _edit_blocks
    return await self.slack_web_client.api_call(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 137, in api_call
    return await self._send(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 168, in _send
    res = await self._request(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 188, in _request
    return await _request_with_session(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_internal_utils.py", line 87, in _request_with_session
    async with session.request(http_verb, api_url, **req_args) as res:
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/client.py", line 1117, in __aenter__
    self._resp = await self._coro
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/client.py", line 520, in _request
    conn = await self._connector.connect(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 535, in connect
    proto = await self._create_connection(req, traces, timeout)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 892, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 1051, in _create_direct_connection
    raise last_exc
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 1020, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 975, in _wrap_create_connection
    raise client_error(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host www.slack.com:443 ssl:<ssl.SSLContext object at 0x7ffa5257ea40> [Connection reset by peer]
ERROR slack_sdk.socket_mode.aiohttp: Failed to run a request listener: argument of type 'NoneType' is not iterable
Traceback (most recent call last):
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 969, in _wrap_create_connection
    return await self._loop.create_connection(*args, **kwargs)  # type: ignore  # noqa
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/base_events.py", line 1050, in create_connection
    transport, protocol = await self._create_connection_transport(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/base_events.py", line 1080, in _create_connection_transport
    await waiter
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/asyncio/selector_events.py", line 846, in _read_ready__data_received
    data = self._sock.recv(self.max_size)
ConnectionResetError: [Errno 54] Connection reset by peer
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 465, in run_skill
    return await skill(event)
  File "/Users/samanthahughes/Library/Application Support/opsdroid/opsdroid-modules/skill/nuke/__init__.py", line 454, in block
    await self.handle_nuke_arm(account=account)
  File "/Users/samanthahughes/Library/Application Support/opsdroid/opsdroid-modules/skill/nuke/__init__.py", line 361, in handle_nuke_arm
    await self.opsdroid.send(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 662, in send
    return await event.connector.send(event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/__init__.py", line 220, in send
    return await self.events[type(event)](self, event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/connector.py", line 300, in _edit_blocks
    return await self.slack_web_client.api_call(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 137, in api_call
    return await self._send(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 168, in _send
    res = await self._request(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_base_client.py", line 188, in _request
    return await _request_with_session(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/web/async_internal_utils.py", line 87, in _request_with_session
    async with session.request(http_verb, api_url, **req_args) as res:
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/client.py", line 1117, in __aenter__
    self._resp = await self._coro
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/client.py", line 520, in _request
    conn = await self._connector.connect(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 535, in connect
    proto = await self._create_connection(req, traces, timeout)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 892, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 1051, in _create_direct_connection
    raise last_exc
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 1020, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/aiohttp/connector.py", line 975, in _wrap_create_connection
    raise client_error(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host www.slack.com:443 ssl:<ssl.SSLContext object at 0x7ffa5257ea40> [Connection reset by peer]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/slack_sdk/socket_mode/async_client.py", line 133, in run_message_listeners
    await listener(self, request)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/connector.py", line 165, in socket_event_handler
    await self.event_handler(payload)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/connector.py", line 149, in event_handler
    await self.opsdroid.parse(e)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 640, in parse
    await asyncio.gather(*tasks)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/parsers/event_type.py", line 61, in parse_event_type
    await opsdroid.run_skill(skill, skill.config, event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 471, in run_skill
    await event.respond(
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/events.py", line 96, in respond
    response_txt = await super().respond(response_event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/events.py", line 147, in respond
    result = await opsdroid.send(event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/core.py", line 662, in send
    return await event.connector.send(event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/__init__.py", line 220, in send
    return await self.events[type(event)](self, event)
  File "/Users/samanthahughes/miniconda3/envs/opsdroid/lib/python3.8/site-packages/opsdroid/connector/slack/connector.py", line 234, in _send_message
    if "thread_ts" in message.linked_event.raw_event:
TypeError: argument of type 'NoneType' is not iterable
^CINFO opsdroid.core: Received stop signal, exiting.
INFO opsdroid.core: Stopping connector slack...
```


Discussion:
hey @FabioRosado 

If you could share, I would be interested to know what does the `message.linked_event` holds, as it seems it does not have the `raw_event` attribute.  It should be safe to add an extra check to see if the `raw_event` actually exists or not, but knowing what the link event holds would give more context on how that exception could be handled.

Also, how do you know it's a rate limit error? Usually Slack gives a "rate_limited" API  error, which I am not seeing on those logs. It looks to me more like a network connection problem to slack
```
Cannot connect to host www.slack.com:443 ssl:<ssl.SSLContext object at 0x7ffa5257ea40> [Connection reset by peer]
During handling of the above exception, another exception occurred:
```


Resolving patch:
diff --git a/opsdroid/connector/slack/connector.py b/opsdroid/connector/slack/connector.py
--- a/opsdroid/connector/slack/connector.py
+++ b/opsdroid/connector/slack/connector.py
@@ -288,13 +288,12 @@ async def _send_message(self, message):
         }
 
         if message.linked_event:
-            if "thread_ts" in message.linked_event.raw_event:
-                if (
-                    message.linked_event.event_id
-                    != message.linked_event.raw_event["thread_ts"]
-                ):
+            raw_event = message.linked_event.raw_event
+
+            if isinstance(raw_event, dict) and "thread_ts" in raw_event:
+                if message.linked_event.event_id != raw_event["thread_ts"]:
                     # Linked Event is inside a thread
-                    data["thread_ts"] = message.linked_event.raw_event["thread_ts"]
+                    data["thread_ts"] = raw_event["thread_ts"]
             elif self.start_thread:
                 data["thread_ts"] = message.linked_event.event_id
 


Test patch: