Repository: opsdroid/opsdroid

Issue:
Websokect connector : message.text always None exception in core.py
Hi Team,

I downloaded the last commit of opsdroid and test it with opsdroid-desktop (last version v0.2.7) in websocket mode.

After the first message saying hello, I've got an exception

```
  File "/home/hicham/Developments/Bots/opsdroid/opsdroid-master/opsdroid/core.py", line 435, in parse
    if message is not None and message.text.strip() != "":
AttributeError: 'NoneType' object has no attribute 'strip'
ERROR aiohttp.server: Unhandled exception

```

## Steps to Reproduce

1- Install the last version of opsdroid (after the #826 patch commit)
2- Start opsdroid-desktop (i tested with v0.2.7)
3- Connect to opsdroid 
4-  Send any message
5- Error occurs

## Expected Functionality
- No crash
- None message should be handled
- message.text must not be None

## Experienced Functionality

The crash produce the following output

```
DEBUG opsdroid.connector.websocket: Starting Websocket connector
INFO opsdroid.core: Opsdroid is now running, press ctrl+c to exit.
INFO opsdroid.web: Started web server on http://127.0.0.1:8080
INFO aiohttp.access: 127.0.0.1 [07/Mar/2019:14:35:22 +0000] "POST /connector/websocket HTTP/1.1" 200 220 "-" "-"
DEBUG opsdroid.connector.websocket: User connected to 3d406d62-40e6-11e9-bd4a-4865ee166460
ERROR aiohttp.server: Error handling request
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File "/usr/lib/python3.7/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File "/home/hicham/Developments/Bots/opsdroid/opsdroid-master/opsdroid/connector/websocket/__init__.py", line 89, in websocket_handler
    await self.opsdroid.parse(message)
  File "/home/hicham/Developments/Bots/opsdroid/opsdroid-master/opsdroid/core.py", line 435, in parse
    if message is not None and message.text.strip() != "":
AttributeError: 'NoneType' object has no attribute 'strip'
ERROR aiohttp.server: Unhandled exception

```

I did a first modification (to handle the case of message is None) in **opsdroid/core.py**

```python
    async def parse(self, message):
        """Parse a string against all skills."""
        self.stats["messages_parsed"] = self.stats["messages_parsed"] + 1
        tasks = []
        _LOGGER.debug("===========> message.text = " + str(message.text))
        if message.text is not None:
            if str(message.text).strip() != "":
                _LOGGER.debug(_("Parsing input: %s"), message.text)
...
...
```

After this modification the crash was avaoided and new exception occurs

```
ERROR opsdroid.core: {'message': 'Task exception was never retrieved', 'exception': TypeError('can only concatenate str (not "ConnectorWebsocket") to str'), 'future': <Task finished coro=<OpsDroid.run_skill() done, defined at /home/hicham/Developments/Bots/opsdroid/opsdroid-master/opsdroid/core.py:328> exception=TypeError('can only concatenate str (not "ConnectorWebsocket") to str')>}
```

After looking after the error, i noticed that in message class, the message initiator has a parameter order different that the order used to init the message object in websocket connector

```python
class Message(NewMessage):
    """A message object.

    Deprecated. Use ``opsdroid.events.Message`` instead.
    """

    def __init__(self, text, user, room, connector,
                 raw_message=None):  # noqa: D401
        """Deprecated opsdroid.message.Message object."""
        super().__init__(text, user, room, connector,
                         raw_event=raw_message)
```

In the new websocket connector line 88 :

```python
    async def new_websocket_handler(self, request):
...
...
        async for msg in websocket:
            if msg.type == aiohttp.WSMsgType.TEXT:
                message = Message(None, socket, self, msg.data)
...
...
```

As you can see, the Noneis sent for text param.

I changed it to

```python
        async for msg in websocket:
            if msg.type == aiohttp.WSMsgType.TEXT:
                message = Message(msg.data, socket, None, self)
```

After this modification, it start working

```
DEBUG opsdroid.connector.websocket: Starting Websocket connector
INFO opsdroid.core: Opsdroid is now running, press ctrl+c to exit.
INFO opsdroid.web: Started web server on http://127.0.0.1:8080
INFO aiohttp.access: 127.0.0.1 [07/Mar/2019:15:32:28 +0000] "POST /connector/websocket HTTP/1.1" 200 220 "-" "-"
DEBUG opsdroid.connector.websocket: User connected to 374e1564-40ee-11e9-a6f2-4865ee166460
DEBUG opsdroid.core: ===========> message.text = hi
DEBUG opsdroid.core: Parsing input: hi
DEBUG opsdroid.connector.websocket: Responding with: 'Hey 374e1564-40ee-11e9-a6f2-4865ee166460' in target 374e1564-40ee-11e9-a6f2-4865ee166460
DEBUG opsdroid.core: ===========> message.text = how are you ?
DEBUG opsdroid.core: Parsing input: how are you ?
DEBUG opsdroid.core: ===========> message.text = hi
DEBUG opsdroid.core: Parsing input: hi
DEBUG opsdroid.connector.websocket: Responding with: 'Hey 374e1564-40ee-11e9-a6f2-4865ee166460' in target 374e1564-40ee-11e9-a6f2-4865ee166460
![bug-websocket](https://user-images.githubusercontent.com/13402022/53971355-0ff30880-40fd-11e9-97c7-dd61052d5f25.png)


``` 

Perhaps more diag is needed.


## Versions
- **Opsdroid version: last one after #826 commit**
- **Python version: 3.7.0 **


Kind Regards
Hicham


Discussion:
@Cadair could you take a look at this?
*mutters something quietly about the dangers of rebasing*

Yep, I will patch this as soon as I get a second.

Resolving patch:
diff --git a/opsdroid/connector/websocket/__init__.py b/opsdroid/connector/websocket/__init__.py
--- a/opsdroid/connector/websocket/__init__.py
+++ b/opsdroid/connector/websocket/__init__.py
@@ -82,10 +82,9 @@ async def websocket_handler(self, request):
         await websocket.prepare(request)
 
         self.active_connections[socket] = websocket
-
         async for msg in websocket:
             if msg.type == aiohttp.WSMsgType.TEXT:
-                message = Message(None, socket, self, msg.data)
+                message = Message(msg.data, None, None, self)
                 await self.opsdroid.parse(message)
             elif msg.type == aiohttp.WSMsgType.ERROR:
                 _LOGGER.error('Websocket connection closed with exception %s',
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -432,22 +432,23 @@ async def parse(self, message):
         """Parse a string against all skills."""
         self.stats["messages_parsed"] = self.stats["messages_parsed"] + 1
         tasks = []
-        if message is not None and message.text.strip() != "":
-            _LOGGER.debug(_("Parsing input: %s"), message.text)
+        if message is not None:
+            if str(message.text).strip():
+                _LOGGER.debug(_("Parsing input: %s"), message.text)
 
-            tasks.append(
-                self.eventloop.create_task(parse_always(self, message)))
-
-            unconstrained_skills = await self._constrain_skills(
-                self.skills, message)
-            ranked_skills = await self.get_ranked_skills(
-                unconstrained_skills, message)
-            if ranked_skills:
                 tasks.append(
-                    self.eventloop.create_task(
-                        self.run_skill(ranked_skills[0]["skill"],
-                                       ranked_skills[0]["config"],
-                                       message)))
+                    self.eventloop.create_task(parse_always(self, message)))
+
+                unconstrained_skills = await self._constrain_skills(
+                    self.skills, message)
+                ranked_skills = await self.get_ranked_skills(
+                    unconstrained_skills, message)
+                if ranked_skills:
+                    tasks.append(
+                        self.eventloop.create_task(
+                            self.run_skill(ranked_skills[0]["skill"],
+                                           ranked_skills[0]["config"],
+                                           message)))
 
         return tasks
 


Test patch: