Repository: opsdroid/opsdroid

Issue:
Regex.group broke after events got merged into core
<!-- 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
After the events PR was merged to Core an `AttributeError` exception is raised when we try to get a regex group.

The issue stars in `opsdroid.core` on [line 467](https://github.com/opsdroid/opsdroid/blob/d1d25927107892008f62e6bf83d4f82fb994736c/opsdroid/core.py#L467)

And possibly something in `opsdroid.events` is not parsing the regex groups.


## Steps to Reproduce
Have a skill that tries to get a regex group from a sentence.


## Expected Functionality
Opsdroid should be able to use that group in the reply.


## Experienced Functionality
An `AttributeError` exception is raised

_Note: This is a very broad issue. I'll be more than happy to dig deeper into it if needed._


Discussion:
Does the event have the `regex` property at all?
I am going to assume tht it might be something related to how the message is being parsed. Before the events that line was `ranked_skills[0]["message"]` so I guess the Message class is just calling the parsers but not any of the specific methods of that parser? 🤔 
I was just about writing and issue about this!

Version 0.15 brokes regex (and parseformat, but let's focus on regex), and I discovered the reason.

In `parse_regex`, regex makes a copy of the Message object instead of directly mutating it:

```python
new_message = copy.copy(message)
new_message.regex = regex
matched_skills.append({
    "score": await calculate_score(
        opts["expression"], opts["score_factor"]),
    "skill": skill,
    "config": skill.config,
    "message": new_message
})
```

Other parsers like the NLU ones directly mutates the original message.

The problem is that Opsdroid ignores the `message` key of the `matched_skills`, as you can see in `core.py -> parse`:

```python
tasks.append(
    self.eventloop.create_task(
        self.run_skill(ranked_skills[0]["skill"],
                       ranked_skills[0]["config"],
                       event)))
```

As you can see, it runs the skill with the original event and ignores the `message` key of the ranked_skills where regex put the new message.

An easy and fast fix is to mutate the original message as before, but I think that we need to think how to redesign this part to avoid that kind of mutations :thinking: 
My suggestion here would be to mutate the original message.

As raised in #933 I'm keen to move from mutating attributes like `message.regex` to adding to a static  dictionary attribute like `message.raw_parses['regex']`.

Resolving patch:
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -464,7 +464,7 @@ async def parse(self, event):
                     self.eventloop.create_task(
                         self.run_skill(ranked_skills[0]["skill"],
                                        ranked_skills[0]["config"],
-                                       event)))
+                                       ranked_skills[0]["message"])))
 
         return tasks
 
diff --git a/opsdroid/parsers/parseformat.py b/opsdroid/parsers/parseformat.py
--- a/opsdroid/parsers/parseformat.py
+++ b/opsdroid/parsers/parseformat.py
@@ -1,7 +1,6 @@
 """A helper function for parsing and executing parse_format skills."""
 
 import logging
-import copy
 
 from parse import parse, search
 
@@ -31,13 +30,12 @@ async def parse_format(opsdroid, skills, message):
                 opts = matcher["parse_format"]
                 result = await match_format(message.text, opts)
                 if result:
-                    new_message = copy.copy(message)
-                    new_message.parse_result = result
+                    message.parse_result = result
                     matched_skills.append({
                         "score": await calculate_score(
                             opts["expression"], opts["score_factor"]),
                         "skill": skill,
                         "config": skill.config,
-                        "message": new_message
+                        "message": message
                     })
     return matched_skills
diff --git a/opsdroid/parsers/regex.py b/opsdroid/parsers/regex.py
--- a/opsdroid/parsers/regex.py
+++ b/opsdroid/parsers/regex.py
@@ -2,7 +2,6 @@
 
 import logging
 import re
-import copy
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -39,13 +38,12 @@ async def parse_regex(opsdroid, skills, message):
                 opts = matcher["regex"]
                 regex = await match_regex(message.text, opts)
                 if regex:
-                    new_message = copy.copy(message)
-                    new_message.regex = regex
+                    message.regex = regex
                     matched_skills.append({
                         "score": await calculate_score(
                             opts["expression"], opts["score_factor"]),
                         "skill": skill,
                         "config": skill.config,
-                        "message": new_message
+                        "message": message
                     })
     return matched_skills


Test patch:
diff --git a/tests/test_parser_format.py b/tests/test_parser_format.py
--- a/tests/test_parser_format.py
+++ b/tests/test_parser_format.py
@@ -35,6 +35,9 @@ async def test_parse_format_match_condition(self):
             message = Message("Hello", "user", "default", mock_connector)
             skills = await parse_format(opsdroid, opsdroid.skills, message)
             self.assertEqual(mock_skill, skills[0]["skill"])
+            assert skills[0]["message"] is message
+            # test that the original object has had a new attribute added
+            assert hasattr(message, "parse_result")
 
     async def test_parse_format_search_condition(self):
         with OpsDroid() as opsdroid:
diff --git a/tests/test_parser_regex.py b/tests/test_parser_regex.py
--- a/tests/test_parser_regex.py
+++ b/tests/test_parser_regex.py
@@ -37,6 +37,9 @@ async def test_parse_regex(self):
 
             skills = await parse_regex(opsdroid, opsdroid.skills, message)
             self.assertEqual(mock_skill, skills[0]["skill"])
+            assert skills[0]["message"] is message
+            # test that the original object has had a new attribute added
+            assert hasattr(message, "regex")
 
     async def test_parse_regex_priority_low(self):
         with OpsDroid() as opsdroid: