We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fixing rasanlu parser : dict object has no attribute matchers issue #860
# Description

When using rasanlu parser with no intents file (no need to train the module, already done) an attribute issue error happened in function _get_all_intents(skills)

...
    matchers = [matcher for skill in skills for matcher in skill.matchers]
  File "/Users/IOBreaker/Developments/Bots/opsdroid/opsdroid/parsers/rasanlu.py", line 19, in <listcomp>
    matchers = [matcher for skill in skills for matcher in skill.matchers]
AttributeError: 'dict' object has no attribute 'matchers'

Fixes #860

After discussion with @jacobtomlinson the decision  was to go using intents provided directly by the skill dict.

Beside, an other correction was added to this fixes according to [rasa_nlu](https://github.com/RasaHQ/rasa_nlu/blob/master/docs/http.rst#post-train) documentation

```
The request should always be sent as application/x-yml regardless of wether you use json or md for the data format. Do not send json as application/json for example.
```

## Status
**READY** | **~UNDER DEVELOPMENT~** | **~ON HOLD~**


## Type of change

- Bug fix (non-breaking change which fixes an issue)

# How Has This Been Tested?

I tested the fix on my environment, no issue detected

# Checklist:

- [x] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation (if applicable)
- [x] I have added tests that prove my fix is effective or that my feature works (test file modification)
- [x] New and existing unit tests pass locally with my changes
- [x] Tox ok

rasanlu parser does not manage the new returned response after training request
# Description

This issue was initially detected by @jhofeditz when testing the fix #881.
The new rasanlu trainer instead of returning a json response after a model training, it return a zip file with all files generated by the trainer.

## Steps to Reproduce
1- Download docker image rasa/rasa_nlu:latest-spacy
2- Start docker image
3- Use a skill that interact with rasanlu and that need rasa to be trained using an intents.yml file
4- activate the skill in your opsdroid config file
5- Start opsdroid 

## Expected Functionality
Opsdroid should be capable to manage json response and zip response

## Experienced Functionality

Crash of opsdroid due to parsing error



## Versions
- **Opsdroid version: v0.14.1+37.ge71ea43**
- **Rasanlu version: 0.15.0a1**
- **Python version: 3.7.2**
- **OS/Docker version: Docker version 18.09.2, build 6247962 **

## Additional Details

**From Rasa side :**
```
2019-03-30 11:37:34 INFO     rasa_nlu.data_router  - Logging of requests is disabled. (No 'request_log' directory configured)
2019-03-30 11:37:34 INFO     __main__  - Started http server on port 5000
2019-03-30 11:37:34+0000 [-] Log opened.
2019-03-30 11:37:34+0000 [-] Site starting on 5000
2019-03-30 11:37:34+0000 [-] Starting factory <twisted.web.server.Site object at 0x7f65ffbff470>
2019-03-30 11:50:43+0000 [-] 2019-03-30 11:50:43 DEBUG    rasa_nlu.data_router  - New training queued
Fitting 2 folds for each of 6 candidates, totalling 12 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done  12 out of  12 | elapsed:    0.0s finished
2019-03-30 11:50:59+0000 [-] "172.17.0.1" - - [30/Mar/2019:11:50:58 +0000] "POST /train?project=ergo&fixed_model_name=1c0badaf3eb8c2bf6546465eadfd8492e7d79c1f0f3520d103d4bc39b4bfc&token=sfgz654zfg546qs4fg646rg64efg HTTP/1.1" 200 11767 "-" "Python/3.7 aiohttp/3.5.4"
2019-03-30 11:53:24+0000 [-] "172.17.0.1" - - [30/Mar/2019:11:53:23 +0000] "GET / HTTP/1.1" 200 29 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0"
2019-03-30 11:53:24+0000 [-] "172.17.0.1" - - [30/Mar/2019:11:53:23 +0000] "GET /favicon.ico HTTP/1.1" 404 233 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0"
2019-03-30 11:54:24+0000 [-] Timing out client: IPv4Address(type='TCP', host='172.17.0.1', port=59948)
```


**From Opsdroid side :**
```
INFO opsdroid.parsers.rasanlu: Starting Rasa NLU training.
INFO opsdroid.parsers.rasanlu: Now training the model. This may take a while...
DEBUG asyncio: Using selector: KqueueSelector
Traceback (most recent call last):
...
  File "/Users/hicham/Developments/Bots/opsdroid-iobreaker/opsdroid/opsdroid/parsers/rasanlu.py", line 117, in train_rasanlu
    result = await resp.json()
  File "/Users/hicham/.virtualenvs/opsdroid/lib/python3.7/site-packages/aiohttp/client_reqrep.py", line 1027, in json
    headers=self.headers)
aiohttp.client_exceptions.ContentTypeError: 0, message='Attempt to decode JSON with unexpected mimetype: application/zip'
```


--- 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/parsers/rasanlu.py`
Content:
```
1 """A helper function for training, parsing and executing Rasa NLU skills."""
2 
3 import logging
4 import json
5 import unicodedata
6 
7 from hashlib import sha256
8 
9 import aiohttp
10 import arrow
11 
12 from opsdroid.const import RASANLU_DEFAULT_URL, RASANLU_DEFAULT_PROJECT
13 
14 _LOGGER = logging.getLogger(__name__)
15 
16 
17 async def _get_all_intents(skills):
18     """Get all skill intents and concatenate into a single markdown string."""
19     intents = [skill["intents"] for skill in skills if skill["intents"] is not None]
20     if not intents:
21         return None
22     intents = "\n\n".join(intents)
23     return unicodedata.normalize("NFKD", intents).encode("ascii")
24 
25 
26 async def _get_intents_fingerprint(intents):
27     """Return a hash of the intents."""
28     return sha256(intents).hexdigest()
29 
30 
31 async def _build_training_url(config):
32     """Build the url for training a Rasa NLU model."""
33     url = "{}/train?project={}&fixed_model_name={}".format(
34         config.get("url", RASANLU_DEFAULT_URL),
35         config.get("project", RASANLU_DEFAULT_PROJECT),
36         config["model"],
37     )
38 
39     if "token" in config:
40         url += "&token={}".format(config["token"])
41 
42     return url
43 
44 
45 async def _build_status_url(config):
46     """Build the url for getting the status of Rasa NLU."""
47     return "{}/status".format(config.get("url", RASANLU_DEFAULT_URL))
48 
49 
50 async def _init_model(config):
51     """Make a request to force Rasa NLU to load the model into memory."""
52     _LOGGER.info(_("Initialising Rasa NLU model."))
53 
54     initialisation_start = arrow.now()
55     result = await call_rasanlu("", config)
56 
57     if result is None:
58         _LOGGER.error(_("Initialisation failed, training failed.."))
59         return False
60 
61     time_taken = int((arrow.now() - initialisation_start).total_seconds())
62     _LOGGER.info(_("Initialisation complete in %s seconds."), time_taken)
63 
64     return True
65 
66 
67 async def _get_existing_models(config):
68     """Get a list of models already trained in the Rasa NLU project."""
69     project = config.get("project", RASANLU_DEFAULT_PROJECT)
70     async with aiohttp.ClientSession() as session:
71         try:
72             resp = await session.get(await _build_status_url(config))
73             if resp.status == 200:
74                 result = await resp.json()
75                 if project in result["available_projects"]:
76                     project_models = result["available_projects"][project]
77                     return project_models["available_models"]
78         except aiohttp.ClientOSError:
79             pass
80     return []
81 
82 
83 async def train_rasanlu(config, skills):
84     """Train a Rasa NLU model based on the loaded skills."""
85     _LOGGER.info(_("Starting Rasa NLU training."))
86     intents = await _get_all_intents(skills)
87     if intents is None:
88         _LOGGER.warning(_("No intents found, skipping training."))
89         return False
90 
91     config["model"] = await _get_intents_fingerprint(intents)
92     if config["model"] in await _get_existing_models(config):
93         _LOGGER.info(_("This model already exists, skipping training..."))
94         await _init_model(config)
95         return True
96 
97     async with aiohttp.ClientSession() as session:
98         _LOGGER.info(_("Now training the model. This may take a while..."))
99 
100         url = await _build_training_url(config)
101 
102         # https://github.com/RasaHQ/rasa_nlu/blob/master/docs/http.rst#post-train
103         # Note : The request should always be sent as
104         # application/x-yml regardless of wether you use
105         # json or md for the data format. Do not send json as
106         # application/json for example.+
107         headers = {"content-type": "application/x-yml"}
108 
109         try:
110             training_start = arrow.now()
111             resp = await session.post(url, data=intents, headers=headers)
112         except aiohttp.client_exceptions.ClientConnectorError:
113             _LOGGER.error(_("Unable to connect to Rasa NLU, training failed."))
114             return False
115 
116         if resp.status == 200:
117             result = await resp.json()
118             if "info" in result and "new model trained" in result["info"]:
119                 time_taken = (arrow.now() - training_start).total_seconds()
120                 _LOGGER.info(
121                     _("Rasa NLU training completed in %s seconds."), int(time_taken)
122                 )
123                 await _init_model(config)
124                 return True
125 
126             _LOGGER.debug(result)
127 
128         _LOGGER.error(_("Bad Rasa NLU response - %s"), await resp.text())
129         _LOGGER.error(_("Rasa NLU training failed."))
130         return False
131 
132 
133 async def call_rasanlu(text, config):
134     """Call the Rasa NLU api and return the response."""
135     async with aiohttp.ClientSession() as session:
136         headers = {}
137         data = {
138             "q": text,
139             "project": config.get("project", "default"),
140             "model": config.get("model", "fallback"),
141         }
142         if "token" in config:
143             data["token"] = config["token"]
144         url = config.get("url", RASANLU_DEFAULT_URL) + "/parse"
145         try:
146             resp = await session.post(url, data=json.dumps(data), headers=headers)
147         except aiohttp.client_exceptions.ClientConnectorError:
148             _LOGGER.error(_("Unable to connect to Rasa NLU"))
149             return None
150         if resp.status == 200:
151             result = await resp.json()
152             _LOGGER.debug(_("Rasa NLU response - %s"), json.dumps(result))
153         else:
154             result = await resp.text()
155             _LOGGER.error(_("Bad Rasa NLU response - %s"), result)
156 
157         return result
158 
159 
160 async def parse_rasanlu(opsdroid, skills, message, config):
161     """Parse a message against all Rasa NLU skills."""
162     matched_skills = []
163     try:
164         result = await call_rasanlu(message.text, config)
165     except aiohttp.ClientOSError:
166         _LOGGER.error(_("No response from Rasa NLU, check your network."))
167         return matched_skills
168 
169     if result == "unauthorized":
170         _LOGGER.error(_("Rasa NLU error - Unauthorised request." "Check your 'token'."))
171         return matched_skills
172 
173     if result is None or "intent" not in result or result["intent"] is None:
174         _LOGGER.error(
175             _("Rasa NLU error - No intent found. Did you " "forget to create one?")
176         )
177         return matched_skills
178 
179     confidence = result["intent"]["confidence"]
180     if "min-score" in config and confidence < config["min-score"]:
181         _LOGGER.info(_("Rasa NLU score lower than min-score"))
182         return matched_skills
183 
184     if result:
185         for skill in skills:
186             for matcher in skill.matchers:
187                 if "rasanlu_intent" in matcher:
188                     if matcher["rasanlu_intent"] == result["intent"]["name"]:
189                         message.rasanlu = result
190                         matched_skills.append(
191                             {
192                                 "score": confidence,
193                                 "skill": skill,
194                                 "config": skill.config,
195                                 "message": message,
196                             }
197                         )
198 
199     return matched_skills
200
```
--- 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/parsers/rasanlu.py b/opsdroid/parsers/rasanlu.py
--- a/opsdroid/parsers/rasanlu.py
+++ b/opsdroid/parsers/rasanlu.py
@@ -114,17 +114,41 @@
             return False
 
         if resp.status == 200:
-            result = await resp.json()
-            if "info" in result and "new model trained" in result["info"]:
+            if resp.content_type == "application/json":
+                result = await resp.json()
+                if "info" in result and "new model trained" in result["info"]:
+                    time_taken = (arrow.now() - training_start).total_seconds()
+                    _LOGGER.info(
+                        _("Rasa NLU training completed in %s seconds."), int(time_taken)
+                    )
+                    await _init_model(config)
+                    return True
+
+                _LOGGER.debug(result)
+            if (
+                resp.content_type == "application/zip"
+                and resp.content_disposition.type == "attachment"
+            ):
                 time_taken = (arrow.now() - training_start).total_seconds()
                 _LOGGER.info(
                     _("Rasa NLU training completed in %s seconds."), int(time_taken)
                 )
                 await _init_model(config)
+                """
+                As inditated in the issue #886, returned zip file is ignored, this can be changed
+                This can be changed in future release if needed
+                Saving model.zip file example :
+                try:
+                    output_file = open("/target/directory/model.zip","wb")
+                    data = await resp.read()
+                    output_file.write(data)
+                    output_file.close()
+                    _LOGGER.debug("Rasa taining model file saved to /target/directory/model.zip")
+                except:
+                    _LOGGER.error("Cannot save rasa taining model file to /target/directory/model.zip")
+                """
                 return True
 
-            _LOGGER.debug(result)
-
         _LOGGER.error(_("Bad Rasa NLU response - %s"), await resp.text())
         _LOGGER.error(_("Rasa NLU training failed."))
         return False

{"golden_diff": "diff --git a/opsdroid/parsers/rasanlu.py b/opsdroid/parsers/rasanlu.py\n--- a/opsdroid/parsers/rasanlu.py\n+++ b/opsdroid/parsers/rasanlu.py\n@@ -114,17 +114,41 @@\n             return False\n \n         if resp.status == 200:\n-            result = await resp.json()\n-            if \"info\" in result and \"new model trained\" in result[\"info\"]:\n+            if resp.content_type == \"application/json\":\n+                result = await resp.json()\n+                if \"info\" in result and \"new model trained\" in result[\"info\"]:\n+                    time_taken = (arrow.now() - training_start).total_seconds()\n+                    _LOGGER.info(\n+                        _(\"Rasa NLU training completed in %s seconds.\"), int(time_taken)\n+                    )\n+                    await _init_model(config)\n+                    return True\n+\n+                _LOGGER.debug(result)\n+            if (\n+                resp.content_type == \"application/zip\"\n+                and resp.content_disposition.type == \"attachment\"\n+            ):\n                 time_taken = (arrow.now() - training_start).total_seconds()\n                 _LOGGER.info(\n                     _(\"Rasa NLU training completed in %s seconds.\"), int(time_taken)\n                 )\n                 await _init_model(config)\n+                \"\"\"\n+                As inditated in the issue #886, returned zip file is ignored, this can be changed\n+                This can be changed in future release if needed\n+                Saving model.zip file example :\n+                try:\n+                    output_file = open(\"/target/directory/model.zip\",\"wb\")\n+                    data = await resp.read()\n+                    output_file.write(data)\n+                    output_file.close()\n+                    _LOGGER.debug(\"Rasa taining model file saved to /target/directory/model.zip\")\n+                except:\n+                    _LOGGER.error(\"Cannot save rasa taining model file to /target/directory/model.zip\")\n+                \"\"\"\n                 return True\n \n-            _LOGGER.debug(result)\n-\n         _LOGGER.error(_(\"Bad Rasa NLU response - %s\"), await resp.text())\n         _LOGGER.error(_(\"Rasa NLU training failed.\"))\n         return False\n", "issue": "Fixing rasanlu parser : dict object has no attribute matchers issue #860\n# Description\r\n\r\nWhen using rasanlu parser with no intents file (no need to train the module, already done) an attribute issue error happened in function _get_all_intents(skills)\r\n\r\n...\r\n    matchers = [matcher for skill in skills for matcher in skill.matchers]\r\n  File \"/Users/IOBreaker/Developments/Bots/opsdroid/opsdroid/parsers/rasanlu.py\", line 19, in <listcomp>\r\n    matchers = [matcher for skill in skills for matcher in skill.matchers]\r\nAttributeError: 'dict' object has no attribute 'matchers'\r\n\r\nFixes #860\r\n\r\nAfter discussion with @jacobtomlinson the decision  was to go using intents provided directly by the skill dict.\r\n\r\nBeside, an other correction was added to this fixes according to [rasa_nlu](https://github.com/RasaHQ/rasa_nlu/blob/master/docs/http.rst#post-train) documentation\r\n\r\n```\r\nThe request should always be sent as application/x-yml regardless of wether you use json or md for the data format. Do not send json as application/json for example.\r\n```\r\n\r\n## Status\r\n**READY** | **~UNDER DEVELOPMENT~** | **~ON HOLD~**\r\n\r\n\r\n## Type of change\r\n\r\n- Bug fix (non-breaking change which fixes an issue)\r\n\r\n# How Has This Been Tested?\r\n\r\nI tested the fix on my environment, no issue detected\r\n\r\n# Checklist:\r\n\r\n- [x] I have performed a self-review of my own code\r\n- [ ] I have made corresponding changes to the documentation (if applicable)\r\n- [x] I have added tests that prove my fix is effective or that my feature works (test file modification)\r\n- [x] New and existing unit tests pass locally with my changes\r\n- [x] Tox ok\r\n\nrasanlu parser does not manage the new returned response after training request\n# Description\r\n\r\nThis issue was initially detected by @jhofeditz when testing the fix #881.\r\nThe new rasanlu trainer instead of returning a json response after a model training, it return a zip file with all files generated by the trainer.\r\n\r\n## Steps to Reproduce\r\n1- Download docker image rasa/rasa_nlu:latest-spacy\r\n2- Start docker image\r\n3- Use a skill that interact with rasanlu and that need rasa to be trained using an intents.yml file\r\n4- activate the skill in your opsdroid config file\r\n5- Start opsdroid \r\n\r\n## Expected Functionality\r\nOpsdroid should be capable to manage json response and zip response\r\n\r\n## Experienced Functionality\r\n\r\nCrash of opsdroid due to parsing error\r\n\r\n\r\n\r\n## Versions\r\n- **Opsdroid version: v0.14.1+37.ge71ea43**\r\n- **Rasanlu version: 0.15.0a1**\r\n- **Python version: 3.7.2**\r\n- **OS/Docker version: Docker version 18.09.2, build 6247962 **\r\n\r\n## Additional Details\r\n\r\n**From Rasa side :**\r\n```\r\n2019-03-30 11:37:34 INFO     rasa_nlu.data_router  - Logging of requests is disabled. (No 'request_log' directory configured)\r\n2019-03-30 11:37:34 INFO     __main__  - Started http server on port 5000\r\n2019-03-30 11:37:34+0000 [-] Log opened.\r\n2019-03-30 11:37:34+0000 [-] Site starting on 5000\r\n2019-03-30 11:37:34+0000 [-] Starting factory <twisted.web.server.Site object at 0x7f65ffbff470>\r\n2019-03-30 11:50:43+0000 [-] 2019-03-30 11:50:43 DEBUG    rasa_nlu.data_router  - New training queued\r\nFitting 2 folds for each of 6 candidates, totalling 12 fits\r\n[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.\r\n[Parallel(n_jobs=1)]: Done  12 out of  12 | elapsed:    0.0s finished\r\n2019-03-30 11:50:59+0000 [-] \"172.17.0.1\" - - [30/Mar/2019:11:50:58 +0000] \"POST /train?project=ergo&fixed_model_name=1c0badaf3eb8c2bf6546465eadfd8492e7d79c1f0f3520d103d4bc39b4bfc&token=sfgz654zfg546qs4fg646rg64efg HTTP/1.1\" 200 11767 \"-\" \"Python/3.7 aiohttp/3.5.4\"\r\n2019-03-30 11:53:24+0000 [-] \"172.17.0.1\" - - [30/Mar/2019:11:53:23 +0000] \"GET / HTTP/1.1\" 200 29 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0\"\r\n2019-03-30 11:53:24+0000 [-] \"172.17.0.1\" - - [30/Mar/2019:11:53:23 +0000] \"GET /favicon.ico HTTP/1.1\" 404 233 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0\"\r\n2019-03-30 11:54:24+0000 [-] Timing out client: IPv4Address(type='TCP', host='172.17.0.1', port=59948)\r\n```\r\n\r\n\r\n**From Opsdroid side :**\r\n```\r\nINFO opsdroid.parsers.rasanlu: Starting Rasa NLU training.\r\nINFO opsdroid.parsers.rasanlu: Now training the model. This may take a while...\r\nDEBUG asyncio: Using selector: KqueueSelector\r\nTraceback (most recent call last):\r\n...\r\n  File \"/Users/hicham/Developments/Bots/opsdroid-iobreaker/opsdroid/opsdroid/parsers/rasanlu.py\", line 117, in train_rasanlu\r\n    result = await resp.json()\r\n  File \"/Users/hicham/.virtualenvs/opsdroid/lib/python3.7/site-packages/aiohttp/client_reqrep.py\", line 1027, in json\r\n    headers=self.headers)\r\naiohttp.client_exceptions.ContentTypeError: 0, message='Attempt to decode JSON with unexpected mimetype: application/zip'\r\n```\r\n\n", "before_files": [{"content": "\"\"\"A helper function for training, parsing and executing Rasa NLU skills.\"\"\"\n\nimport logging\nimport json\nimport unicodedata\n\nfrom hashlib import sha256\n\nimport aiohttp\nimport arrow\n\nfrom opsdroid.const import RASANLU_DEFAULT_URL, RASANLU_DEFAULT_PROJECT\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def _get_all_intents(skills):\n    \"\"\"Get all skill intents and concatenate into a single markdown string.\"\"\"\n    intents = [skill[\"intents\"] for skill in skills if skill[\"intents\"] is not None]\n    if not intents:\n        return None\n    intents = \"\\n\\n\".join(intents)\n    return unicodedata.normalize(\"NFKD\", intents).encode(\"ascii\")\n\n\nasync def _get_intents_fingerprint(intents):\n    \"\"\"Return a hash of the intents.\"\"\"\n    return sha256(intents).hexdigest()\n\n\nasync def _build_training_url(config):\n    \"\"\"Build the url for training a Rasa NLU model.\"\"\"\n    url = \"{}/train?project={}&fixed_model_name={}\".format(\n        config.get(\"url\", RASANLU_DEFAULT_URL),\n        config.get(\"project\", RASANLU_DEFAULT_PROJECT),\n        config[\"model\"],\n    )\n\n    if \"token\" in config:\n        url += \"&token={}\".format(config[\"token\"])\n\n    return url\n\n\nasync def _build_status_url(config):\n    \"\"\"Build the url for getting the status of Rasa NLU.\"\"\"\n    return \"{}/status\".format(config.get(\"url\", RASANLU_DEFAULT_URL))\n\n\nasync def _init_model(config):\n    \"\"\"Make a request to force Rasa NLU to load the model into memory.\"\"\"\n    _LOGGER.info(_(\"Initialising Rasa NLU model.\"))\n\n    initialisation_start = arrow.now()\n    result = await call_rasanlu(\"\", config)\n\n    if result is None:\n        _LOGGER.error(_(\"Initialisation failed, training failed..\"))\n        return False\n\n    time_taken = int((arrow.now() - initialisation_start).total_seconds())\n    _LOGGER.info(_(\"Initialisation complete in %s seconds.\"), time_taken)\n\n    return True\n\n\nasync def _get_existing_models(config):\n    \"\"\"Get a list of models already trained in the Rasa NLU project.\"\"\"\n    project = config.get(\"project\", RASANLU_DEFAULT_PROJECT)\n    async with aiohttp.ClientSession() as session:\n        try:\n            resp = await session.get(await _build_status_url(config))\n            if resp.status == 200:\n                result = await resp.json()\n                if project in result[\"available_projects\"]:\n                    project_models = result[\"available_projects\"][project]\n                    return project_models[\"available_models\"]\n        except aiohttp.ClientOSError:\n            pass\n    return []\n\n\nasync def train_rasanlu(config, skills):\n    \"\"\"Train a Rasa NLU model based on the loaded skills.\"\"\"\n    _LOGGER.info(_(\"Starting Rasa NLU training.\"))\n    intents = await _get_all_intents(skills)\n    if intents is None:\n        _LOGGER.warning(_(\"No intents found, skipping training.\"))\n        return False\n\n    config[\"model\"] = await _get_intents_fingerprint(intents)\n    if config[\"model\"] in await _get_existing_models(config):\n        _LOGGER.info(_(\"This model already exists, skipping training...\"))\n        await _init_model(config)\n        return True\n\n    async with aiohttp.ClientSession() as session:\n        _LOGGER.info(_(\"Now training the model. This may take a while...\"))\n\n        url = await _build_training_url(config)\n\n        # https://github.com/RasaHQ/rasa_nlu/blob/master/docs/http.rst#post-train\n        # Note : The request should always be sent as\n        # application/x-yml regardless of wether you use\n        # json or md for the data format. Do not send json as\n        # application/json for example.+\n        headers = {\"content-type\": \"application/x-yml\"}\n\n        try:\n            training_start = arrow.now()\n            resp = await session.post(url, data=intents, headers=headers)\n        except aiohttp.client_exceptions.ClientConnectorError:\n            _LOGGER.error(_(\"Unable to connect to Rasa NLU, training failed.\"))\n            return False\n\n        if resp.status == 200:\n            result = await resp.json()\n            if \"info\" in result and \"new model trained\" in result[\"info\"]:\n                time_taken = (arrow.now() - training_start).total_seconds()\n                _LOGGER.info(\n                    _(\"Rasa NLU training completed in %s seconds.\"), int(time_taken)\n                )\n                await _init_model(config)\n                return True\n\n            _LOGGER.debug(result)\n\n        _LOGGER.error(_(\"Bad Rasa NLU response - %s\"), await resp.text())\n        _LOGGER.error(_(\"Rasa NLU training failed.\"))\n        return False\n\n\nasync def call_rasanlu(text, config):\n    \"\"\"Call the Rasa NLU api and return the response.\"\"\"\n    async with aiohttp.ClientSession() as session:\n        headers = {}\n        data = {\n            \"q\": text,\n            \"project\": config.get(\"project\", \"default\"),\n            \"model\": config.get(\"model\", \"fallback\"),\n        }\n        if \"token\" in config:\n            data[\"token\"] = config[\"token\"]\n        url = config.get(\"url\", RASANLU_DEFAULT_URL) + \"/parse\"\n        try:\n            resp = await session.post(url, data=json.dumps(data), headers=headers)\n        except aiohttp.client_exceptions.ClientConnectorError:\n            _LOGGER.error(_(\"Unable to connect to Rasa NLU\"))\n            return None\n        if resp.status == 200:\n            result = await resp.json()\n            _LOGGER.debug(_(\"Rasa NLU response - %s\"), json.dumps(result))\n        else:\n            result = await resp.text()\n            _LOGGER.error(_(\"Bad Rasa NLU response - %s\"), result)\n\n        return result\n\n\nasync def parse_rasanlu(opsdroid, skills, message, config):\n    \"\"\"Parse a message against all Rasa NLU skills.\"\"\"\n    matched_skills = []\n    try:\n        result = await call_rasanlu(message.text, config)\n    except aiohttp.ClientOSError:\n        _LOGGER.error(_(\"No response from Rasa NLU, check your network.\"))\n        return matched_skills\n\n    if result == \"unauthorized\":\n        _LOGGER.error(_(\"Rasa NLU error - Unauthorised request.\" \"Check your 'token'.\"))\n        return matched_skills\n\n    if result is None or \"intent\" not in result or result[\"intent\"] is None:\n        _LOGGER.error(\n            _(\"Rasa NLU error - No intent found. Did you \" \"forget to create one?\")\n        )\n        return matched_skills\n\n    confidence = result[\"intent\"][\"confidence\"]\n    if \"min-score\" in config and confidence < config[\"min-score\"]:\n        _LOGGER.info(_(\"Rasa NLU score lower than min-score\"))\n        return matched_skills\n\n    if result:\n        for skill in skills:\n            for matcher in skill.matchers:\n                if \"rasanlu_intent\" in matcher:\n                    if matcher[\"rasanlu_intent\"] == result[\"intent\"][\"name\"]:\n                        message.rasanlu = result\n                        matched_skills.append(\n                            {\n                                \"score\": confidence,\n                                \"skill\": skill,\n                                \"config\": skill.config,\n                                \"message\": message,\n                            }\n                        )\n\n    return matched_skills\n", "path": "opsdroid/parsers/rasanlu.py"}], "after_files": [{"content": "\"\"\"A helper function for training, parsing and executing Rasa NLU skills.\"\"\"\n\nimport logging\nimport json\nimport unicodedata\n\nfrom hashlib import sha256\n\nimport aiohttp\nimport arrow\n\nfrom opsdroid.const import RASANLU_DEFAULT_URL, RASANLU_DEFAULT_PROJECT\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def _get_all_intents(skills):\n    \"\"\"Get all skill intents and concatenate into a single markdown string.\"\"\"\n    intents = [skill[\"intents\"] for skill in skills if skill[\"intents\"] is not None]\n    if not intents:\n        return None\n    intents = \"\\n\\n\".join(intents)\n    return unicodedata.normalize(\"NFKD\", intents).encode(\"ascii\")\n\n\nasync def _get_intents_fingerprint(intents):\n    \"\"\"Return a hash of the intents.\"\"\"\n    return sha256(intents).hexdigest()\n\n\nasync def _build_training_url(config):\n    \"\"\"Build the url for training a Rasa NLU model.\"\"\"\n    url = \"{}/train?project={}&fixed_model_name={}\".format(\n        config.get(\"url\", RASANLU_DEFAULT_URL),\n        config.get(\"project\", RASANLU_DEFAULT_PROJECT),\n        config[\"model\"],\n    )\n\n    if \"token\" in config:\n        url += \"&token={}\".format(config[\"token\"])\n\n    return url\n\n\nasync def _build_status_url(config):\n    \"\"\"Build the url for getting the status of Rasa NLU.\"\"\"\n    return \"{}/status\".format(config.get(\"url\", RASANLU_DEFAULT_URL))\n\n\nasync def _init_model(config):\n    \"\"\"Make a request to force Rasa NLU to load the model into memory.\"\"\"\n    _LOGGER.info(_(\"Initialising Rasa NLU model.\"))\n\n    initialisation_start = arrow.now()\n    result = await call_rasanlu(\"\", config)\n\n    if result is None:\n        _LOGGER.error(_(\"Initialisation failed, training failed..\"))\n        return False\n\n    time_taken = int((arrow.now() - initialisation_start).total_seconds())\n    _LOGGER.info(_(\"Initialisation complete in %s seconds.\"), time_taken)\n\n    return True\n\n\nasync def _get_existing_models(config):\n    \"\"\"Get a list of models already trained in the Rasa NLU project.\"\"\"\n    project = config.get(\"project\", RASANLU_DEFAULT_PROJECT)\n    async with aiohttp.ClientSession() as session:\n        try:\n            resp = await session.get(await _build_status_url(config))\n            if resp.status == 200:\n                result = await resp.json()\n                if project in result[\"available_projects\"]:\n                    project_models = result[\"available_projects\"][project]\n                    return project_models[\"available_models\"]\n        except aiohttp.ClientOSError:\n            pass\n    return []\n\n\nasync def train_rasanlu(config, skills):\n    \"\"\"Train a Rasa NLU model based on the loaded skills.\"\"\"\n    _LOGGER.info(_(\"Starting Rasa NLU training.\"))\n    intents = await _get_all_intents(skills)\n    if intents is None:\n        _LOGGER.warning(_(\"No intents found, skipping training.\"))\n        return False\n\n    config[\"model\"] = await _get_intents_fingerprint(intents)\n    if config[\"model\"] in await _get_existing_models(config):\n        _LOGGER.info(_(\"This model already exists, skipping training...\"))\n        await _init_model(config)\n        return True\n\n    async with aiohttp.ClientSession() as session:\n        _LOGGER.info(_(\"Now training the model. This may take a while...\"))\n\n        url = await _build_training_url(config)\n\n        # https://github.com/RasaHQ/rasa_nlu/blob/master/docs/http.rst#post-train\n        # Note : The request should always be sent as\n        # application/x-yml regardless of wether you use\n        # json or md for the data format. Do not send json as\n        # application/json for example.+\n        headers = {\"content-type\": \"application/x-yml\"}\n\n        try:\n            training_start = arrow.now()\n            resp = await session.post(url, data=intents, headers=headers)\n        except aiohttp.client_exceptions.ClientConnectorError:\n            _LOGGER.error(_(\"Unable to connect to Rasa NLU, training failed.\"))\n            return False\n\n        if resp.status == 200:\n            if resp.content_type == \"application/json\":\n                result = await resp.json()\n                if \"info\" in result and \"new model trained\" in result[\"info\"]:\n                    time_taken = (arrow.now() - training_start).total_seconds()\n                    _LOGGER.info(\n                        _(\"Rasa NLU training completed in %s seconds.\"), int(time_taken)\n                    )\n                    await _init_model(config)\n                    return True\n\n                _LOGGER.debug(result)\n            if (\n                resp.content_type == \"application/zip\"\n                and resp.content_disposition.type == \"attachment\"\n            ):\n                time_taken = (arrow.now() - training_start).total_seconds()\n                _LOGGER.info(\n                    _(\"Rasa NLU training completed in %s seconds.\"), int(time_taken)\n                )\n                await _init_model(config)\n                \"\"\"\n                As inditated in the issue #886, returned zip file is ignored, this can be changed\n                This can be changed in future release if needed\n                Saving model.zip file example :\n                try:\n                    output_file = open(\"/target/directory/model.zip\",\"wb\")\n                    data = await resp.read()\n                    output_file.write(data)\n                    output_file.close()\n                    _LOGGER.debug(\"Rasa taining model file saved to /target/directory/model.zip\")\n                except:\n                    _LOGGER.error(\"Cannot save rasa taining model file to /target/directory/model.zip\")\n                \"\"\"\n                return True\n\n        _LOGGER.error(_(\"Bad Rasa NLU response - %s\"), await resp.text())\n        _LOGGER.error(_(\"Rasa NLU training failed.\"))\n        return False\n\n\nasync def call_rasanlu(text, config):\n    \"\"\"Call the Rasa NLU api and return the response.\"\"\"\n    async with aiohttp.ClientSession() as session:\n        headers = {}\n        data = {\n            \"q\": text,\n            \"project\": config.get(\"project\", \"default\"),\n            \"model\": config.get(\"model\", \"fallback\"),\n        }\n        if \"token\" in config:\n            data[\"token\"] = config[\"token\"]\n        url = config.get(\"url\", RASANLU_DEFAULT_URL) + \"/parse\"\n        try:\n            resp = await session.post(url, data=json.dumps(data), headers=headers)\n        except aiohttp.client_exceptions.ClientConnectorError:\n            _LOGGER.error(_(\"Unable to connect to Rasa NLU\"))\n            return None\n        if resp.status == 200:\n            result = await resp.json()\n            _LOGGER.debug(_(\"Rasa NLU response - %s\"), json.dumps(result))\n        else:\n            result = await resp.text()\n            _LOGGER.error(_(\"Bad Rasa NLU response - %s\"), result)\n\n        return result\n\n\nasync def parse_rasanlu(opsdroid, skills, message, config):\n    \"\"\"Parse a message against all Rasa NLU skills.\"\"\"\n    matched_skills = []\n    try:\n        result = await call_rasanlu(message.text, config)\n    except aiohttp.ClientOSError:\n        _LOGGER.error(_(\"No response from Rasa NLU, check your network.\"))\n        return matched_skills\n\n    if result == \"unauthorized\":\n        _LOGGER.error(_(\"Rasa NLU error - Unauthorised request.\" \"Check your 'token'.\"))\n        return matched_skills\n\n    if result is None or \"intent\" not in result or result[\"intent\"] is None:\n        _LOGGER.error(\n            _(\"Rasa NLU error - No intent found. Did you \" \"forget to create one?\")\n        )\n        return matched_skills\n\n    confidence = result[\"intent\"][\"confidence\"]\n    if \"min-score\" in config and confidence < config[\"min-score\"]:\n        _LOGGER.info(_(\"Rasa NLU score lower than min-score\"))\n        return matched_skills\n\n    if result:\n        for skill in skills:\n            for matcher in skill.matchers:\n                if \"rasanlu_intent\" in matcher:\n                    if matcher[\"rasanlu_intent\"] == result[\"intent\"][\"name\"]:\n                        message.rasanlu = result\n                        matched_skills.append(\n                            {\n                                \"score\": confidence,\n                                \"skill\": skill,\n                                \"config\": skill.config,\n                                \"message\": message,\n                            }\n                        )\n\n    return matched_skills\n", "path": "opsdroid/parsers/rasanlu.py"}]}