We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Telegram connector can not handle the File event type
<!-- Before you post an issue or if you are unsure about something join our matrix channel https://riot.im/app/#/room/#opsdroid-general:matrix.org and ask away! We are more than happy to help you. -->
# Description
Telegram connector does not handle File events.

## Steps to Reproduce
```
@match_regex(r'Please, send me a pdf file')
    async def sendDocument(self, message):
        path= '/file.pdf' 

        with open(path, 'rb') as file:
            fileEvent = File(file_bytes= file, mimetype= 'application/pdf', target= {'id': message.target})
            await message.respond( fileEvent )

```

## Expected Functionality
The connector should handle the File event type.


## Experienced Functionality
You get the following error

> `TypeError: Connector <class 'opsdroid.connector.telegram.ConnectorTelegram'> can not handle the 'File' event type.`

## Versions
- **Opsdroid version:** v0.17.1
- **Python version:** 3.7
- **OS/Docker version:** Fedora 31

## Configuration File
Not relevant, I think

## Additional Details
The solution should be quite similar to [Adds Image support for Telegram #929](https://github.com/opsdroid/opsdroid/pull/929)


<!-- 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/telegram/__init__.py`
Content:
```
1 """A connector for Telegram."""
2 import asyncio
3 import logging
4 import aiohttp
5 from voluptuous import Required
6 
7 from opsdroid.connector import Connector, register_event
8 from opsdroid.events import Message, Image
9 
10 
11 _LOGGER = logging.getLogger(__name__)
12 CONFIG_SCHEMA = {
13     Required("token"): str,
14     "update-interval": float,
15     "default-user": str,
16     "whitelisted-users": list,
17 }
18 
19 
20 class ConnectorTelegram(Connector):
21     """A connector the the char service Telegram."""
22 
23     def __init__(self, config, opsdroid=None):
24         """Create the connector.
25 
26         Args:
27             config (dict): configuration settings from the
28                 file config.yaml.
29             opsdroid (OpsDroid): An instance of opsdroid.core.
30 
31         """
32         _LOGGER.debug(_("Loaded Telegram Connector"))
33         super().__init__(config, opsdroid=opsdroid)
34         self.name = "telegram"
35         self.opsdroid = opsdroid
36         self.latest_update = None
37         self.listening = True
38         self.default_user = config.get("default-user", None)
39         self.default_target = self.default_user
40         self.whitelisted_users = config.get("whitelisted-users", None)
41         self.update_interval = config.get("update-interval", 1)
42         self.session = None
43         self._closing = asyncio.Event()
44         self.loop = asyncio.get_event_loop()
45 
46         try:
47             self.token = config["token"]
48         except (KeyError, AttributeError):
49             _LOGGER.error(
50                 _(
51                     "Unable to login: Access token is missing. Telegram connector will be unavailable."
52                 )
53             )
54 
55     @staticmethod
56     def get_user(response):
57         """Get user from response.
58 
59         The API response is different depending on how
60         the bot is set up and where the message is coming
61         from. This method was created to keep if/else
62         statements to a minium on  _parse_message.
63 
64         Args:
65             response (dict): Response returned by aiohttp.ClientSession.
66 
67         """
68         user = None
69         user_id = None
70 
71         if "username" in response["message"]["from"]:
72             user = response["message"]["from"]["username"]
73 
74         elif "first_name" in response["message"]["from"]:
75             user = response["message"]["from"]["first_name"]
76         user_id = response["message"]["from"]["id"]
77 
78         return user, user_id
79 
80     def handle_user_permission(self, response, user):
81         """Handle user permissions.
82 
83         This will check if the user that tried to talk with
84         the bot is allowed to do so. It will also work with
85         userid to improve security.
86 
87         """
88         user_id = response["message"]["from"]["id"]
89 
90         if (
91             not self.whitelisted_users
92             or user in self.whitelisted_users
93             or user_id in self.whitelisted_users
94         ):
95             return True
96 
97         return False
98 
99     def build_url(self, method):
100         """Build the url to connect to the API.
101 
102         Args:
103             method (string): API call end point.
104 
105         Return:
106             String that represents the full API url.
107 
108         """
109         return "https://api.telegram.org/bot{}/{}".format(self.token, method)
110 
111     async def delete_webhook(self):
112         """Delete Telegram webhook.
113 
114         The Telegram api will thrown an 409 error when an webhook is
115         active and a call to getUpdates is made. This method will
116         try to request the deletion of the webhook to make the getUpdate
117         request possible.
118 
119         """
120         _LOGGER.debug(_("Sending deleteWebhook request to Telegram..."))
121         resp = await self.session.get(self.build_url("deleteWebhook"))
122 
123         if resp.status == 200:
124             _LOGGER.debug(_("Telegram webhook deleted successfully."))
125         else:
126             _LOGGER.debug(_("Unable to delete webhook."))
127 
128     async def connect(self):
129         """Connect to Telegram.
130 
131         This method is not an authorization call. It basically
132         checks if the API token was provided and makes an API
133         call to Telegram and evaluates the status of the call.
134 
135         """
136 
137         _LOGGER.debug(_("Connecting to Telegram."))
138         self.session = aiohttp.ClientSession()
139 
140         resp = await self.session.get(self.build_url("getMe"))
141 
142         if resp.status != 200:
143             _LOGGER.error(_("Unable to connect."))
144             _LOGGER.error(_("Telegram error %s, %s."), resp.status, resp.text)
145         else:
146             json = await resp.json()
147             _LOGGER.debug(json)
148             _LOGGER.debug(_("Connected to Telegram as %s."), json["result"]["username"])
149 
150     async def _parse_message(self, response):
151         """Handle logic to parse a received message.
152 
153         Since everyone can send a private message to any user/bot
154         in Telegram, this method allows to set a list of whitelisted
155         users that can interact with the bot. If any other user tries
156         to interact with the bot the command is not parsed and instead
157         the bot will inform that user that he is not allowed to talk
158         with the bot.
159 
160         We also set self.latest_update to +1 in order to get the next
161         available message (or an empty {} if no message has been received
162         yet) with the method self._get_messages().
163 
164         Args:
165             response (dict): Response returned by aiohttp.ClientSession.
166 
167         """
168         for result in response["result"]:
169             _LOGGER.debug(result)
170 
171             if result.get("edited_message", None):
172                 result["message"] = result.pop("edited_message")
173             if result.get("channel_post", None) or result.get(
174                 "edited_channel_post", None
175             ):
176                 self.latest_update = result["update_id"] + 1
177                 _LOGGER.debug(
178                     _("Channel message parsing not supported " "- Ignoring message.")
179                 )
180             elif "message" in result and "text" in result["message"]:
181                 user, user_id = self.get_user(result)
182                 message = Message(
183                     text=result["message"]["text"],
184                     user=user,
185                     user_id=user_id,
186                     target=result["message"]["chat"]["id"],
187                     connector=self,
188                 )
189 
190                 if self.handle_user_permission(result, user):
191                     await self.opsdroid.parse(message)
192                 else:
193                     message.text = (
194                         "Sorry, you're not allowed " "to speak with this bot."
195                     )
196                     await self.send(message)
197                 self.latest_update = result["update_id"] + 1
198             elif (
199                 "message" in result
200                 and "sticker" in result["message"]
201                 and "emoji" in result["message"]["sticker"]
202             ):
203                 self.latest_update = result["update_id"] + 1
204                 _LOGGER.debug(
205                     _("Emoji message parsing not supported - Ignoring message.")
206                 )
207             else:
208                 _LOGGER.error(_("Unable to parse the message."))
209 
210     async def _get_messages(self):
211         """Connect to the Telegram API.
212 
213         Uses an aiohttp ClientSession to connect to Telegram API
214         and get the latest messages from the chat service.
215 
216         The data["offset"] is used to consume every new message, the API
217         returns an  int - "update_id" value. In order to get the next
218         message this value needs to be increased by 1 the next time
219         the API is called. If no new messages exists the API will just
220         return an empty {}.
221 
222         """
223         data = {}
224         if self.latest_update is not None:
225             data["offset"] = self.latest_update
226 
227         await asyncio.sleep(self.update_interval)
228         resp = await self.session.get(self.build_url("getUpdates"), params=data)
229 
230         if resp.status == 409:
231             _LOGGER.info(
232                 _(
233                     "Can't get updates because previous webhook is still active. Will try to delete webhook."
234                 )
235             )
236             await self.delete_webhook()
237 
238         if resp.status != 200:
239             _LOGGER.error(_("Telegram error %s, %s."), resp.status, resp.text)
240             self.listening = False
241         else:
242             json = await resp.json()
243 
244             await self._parse_message(json)
245 
246     async def get_messages_loop(self):
247         """Listen for and parse new messages.
248 
249         The bot will always listen to all opened chat windows,
250         as long as opsdroid is running. Since anyone can start
251         a new chat with the bot is recommended that a list of
252         users to be whitelisted be provided in config.yaml.
253 
254         The method will sleep asynchronously at the end of
255         every loop. The time can either be specified in the
256         config.yaml with the param update-interval - this
257         defaults to 1 second.
258 
259         """
260         while self.listening:
261             await self._get_messages()
262 
263     async def listen(self):
264         """Listen method of the connector.
265 
266         Every connector has to implement the listen method. When an
267         infinite loop is running, it becomes hard to cancel this task.
268         So we are creating a task and set it on a variable so we can
269         cancel the task.
270 
271         """
272         message_getter = self.loop.create_task(await self.get_messages_loop())
273         await self._closing.wait()
274         message_getter.cancel()
275 
276     @register_event(Message)
277     async def send_message(self, message):
278         """Respond with a message.
279 
280         Args:
281             message (object): An instance of Message.
282 
283         """
284         _LOGGER.debug(
285             _("Responding with: '%s' at target: '%s'"), message.text, message.target
286         )
287 
288         data = dict()
289         data["chat_id"] = message.target
290         data["text"] = message.text
291         resp = await self.session.post(self.build_url("sendMessage"), data=data)
292         if resp.status == 200:
293             _LOGGER.debug(_("Successfully responded."))
294         else:
295             _LOGGER.error(_("Unable to respond."))
296 
297     @register_event(Image)
298     async def send_image(self, file_event):
299         """Send Image to Telegram.
300 
301         Gets the chat id from the channel and then
302         sends the bytes of the image as multipart/form-data.
303 
304         """
305         data = aiohttp.FormData()
306         data.add_field(
307             "chat_id", str(file_event.target["id"]), content_type="multipart/form-data"
308         )
309         data.add_field(
310             "photo",
311             await file_event.get_file_bytes(),
312             content_type="multipart/form-data",
313         )
314 
315         resp = await self.session.post(self.build_url("sendPhoto"), data=data)
316         if resp.status == 200:
317             _LOGGER.debug(_("Sent %s image successfully."), file_event.name)
318         else:
319             _LOGGER.debug(_("Unable to send image - Status Code %s."), resp.status)
320 
321     async def disconnect(self):
322         """Disconnect from Telegram.
323 
324         Stops the infinite loop found in self._listen(), closes
325         aiohttp session.
326 
327         """
328         self.listening = False
329         self._closing.set()
330         await self.session.close()
331
```
--- 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/telegram/__init__.py b/opsdroid/connector/telegram/__init__.py
--- a/opsdroid/connector/telegram/__init__.py
+++ b/opsdroid/connector/telegram/__init__.py
@@ -5,7 +5,7 @@
 from voluptuous import Required
 
 from opsdroid.connector import Connector, register_event
-from opsdroid.events import Message, Image
+from opsdroid.events import Message, Image, File
 
 
 _LOGGER = logging.getLogger(__name__)
@@ -318,6 +318,30 @@
         else:
             _LOGGER.debug(_("Unable to send image - Status Code %s."), resp.status)
 
+    @register_event(File)
+    async def send_file(self, file_event):
+        """Send File to Telegram.
+
+        Gets the chat id from the channel and then
+        sends the bytes of the file as multipart/form-data.
+
+        """
+        data = aiohttp.FormData()
+        data.add_field(
+            "chat_id", str(file_event.target["id"]), content_type="multipart/form-data"
+        )
+        data.add_field(
+            "document",
+            await file_event.get_file_bytes(),
+            content_type="multipart/form-data",
+        )
+
+        resp = await self.session.post(self.build_url("sendDocument"), data=data)
+        if resp.status == 200:
+            _LOGGER.debug(_("Sent %s file successfully."), file_event.name)
+        else:
+            _LOGGER.debug(_("Unable to send file - Status Code %s."), resp.status)
+
     async def disconnect(self):
         """Disconnect from Telegram.

{"golden_diff": "diff --git a/opsdroid/connector/telegram/__init__.py b/opsdroid/connector/telegram/__init__.py\n--- a/opsdroid/connector/telegram/__init__.py\n+++ b/opsdroid/connector/telegram/__init__.py\n@@ -5,7 +5,7 @@\n from voluptuous import Required\n \n from opsdroid.connector import Connector, register_event\n-from opsdroid.events import Message, Image\n+from opsdroid.events import Message, Image, File\n \n \n _LOGGER = logging.getLogger(__name__)\n@@ -318,6 +318,30 @@\n         else:\n             _LOGGER.debug(_(\"Unable to send image - Status Code %s.\"), resp.status)\n \n+    @register_event(File)\n+    async def send_file(self, file_event):\n+        \"\"\"Send File to Telegram.\n+\n+        Gets the chat id from the channel and then\n+        sends the bytes of the file as multipart/form-data.\n+\n+        \"\"\"\n+        data = aiohttp.FormData()\n+        data.add_field(\n+            \"chat_id\", str(file_event.target[\"id\"]), content_type=\"multipart/form-data\"\n+        )\n+        data.add_field(\n+            \"document\",\n+            await file_event.get_file_bytes(),\n+            content_type=\"multipart/form-data\",\n+        )\n+\n+        resp = await self.session.post(self.build_url(\"sendDocument\"), data=data)\n+        if resp.status == 200:\n+            _LOGGER.debug(_(\"Sent %s file successfully.\"), file_event.name)\n+        else:\n+            _LOGGER.debug(_(\"Unable to send file - Status Code %s.\"), resp.status)\n+\n     async def disconnect(self):\n         \"\"\"Disconnect from Telegram.\n", "issue": "Telegram connector can not handle the File event type\n<!-- Before you post an issue or if you are unsure about something join our matrix channel https://riot.im/app/#/room/#opsdroid-general:matrix.org and ask away! We are more than happy to help you. -->\r\n# Description\r\nTelegram connector does not handle File events.\r\n\r\n## Steps to Reproduce\r\n```\r\n@match_regex(r'Please, send me a pdf file')\r\n    async def sendDocument(self, message):\r\n        path= '/file.pdf' \r\n\r\n        with open(path, 'rb') as file:\r\n            fileEvent = File(file_bytes= file, mimetype= 'application/pdf', target= {'id': message.target})\r\n            await message.respond( fileEvent )\r\n\r\n```\r\n\r\n## Expected Functionality\r\nThe connector should handle the File event type.\r\n\r\n\r\n## Experienced Functionality\r\nYou get the following error\r\n\r\n> `TypeError: Connector <class 'opsdroid.connector.telegram.ConnectorTelegram'> can not handle the 'File' event type.`\r\n\r\n## Versions\r\n- **Opsdroid version:** v0.17.1\r\n- **Python version:** 3.7\r\n- **OS/Docker version:** Fedora 31\r\n\r\n## Configuration File\r\nNot relevant, I think\r\n\r\n## Additional Details\r\nThe solution should be quite similar to [Adds Image support for Telegram #929](https://github.com/opsdroid/opsdroid/pull/929)\r\n\r\n\r\n<!-- Love opsdroid? Please consider supporting our collective:\r\n +\ud83d\udc49  https://opencollective.com/opsdroid/donate -->\r\n\n", "before_files": [{"content": "\"\"\"A connector for Telegram.\"\"\"\nimport asyncio\nimport logging\nimport aiohttp\nfrom voluptuous import Required\n\nfrom opsdroid.connector import Connector, register_event\nfrom opsdroid.events import Message, Image\n\n\n_LOGGER = logging.getLogger(__name__)\nCONFIG_SCHEMA = {\n    Required(\"token\"): str,\n    \"update-interval\": float,\n    \"default-user\": str,\n    \"whitelisted-users\": list,\n}\n\n\nclass ConnectorTelegram(Connector):\n    \"\"\"A connector the the char service Telegram.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Create the connector.\n\n        Args:\n            config (dict): configuration settings from the\n                file config.yaml.\n            opsdroid (OpsDroid): An instance of opsdroid.core.\n\n        \"\"\"\n        _LOGGER.debug(_(\"Loaded Telegram Connector\"))\n        super().__init__(config, opsdroid=opsdroid)\n        self.name = \"telegram\"\n        self.opsdroid = opsdroid\n        self.latest_update = None\n        self.listening = True\n        self.default_user = config.get(\"default-user\", None)\n        self.default_target = self.default_user\n        self.whitelisted_users = config.get(\"whitelisted-users\", None)\n        self.update_interval = config.get(\"update-interval\", 1)\n        self.session = None\n        self._closing = asyncio.Event()\n        self.loop = asyncio.get_event_loop()\n\n        try:\n            self.token = config[\"token\"]\n        except (KeyError, AttributeError):\n            _LOGGER.error(\n                _(\n                    \"Unable to login: Access token is missing. Telegram connector will be unavailable.\"\n                )\n            )\n\n    @staticmethod\n    def get_user(response):\n        \"\"\"Get user from response.\n\n        The API response is different depending on how\n        the bot is set up and where the message is coming\n        from. This method was created to keep if/else\n        statements to a minium on  _parse_message.\n\n        Args:\n            response (dict): Response returned by aiohttp.ClientSession.\n\n        \"\"\"\n        user = None\n        user_id = None\n\n        if \"username\" in response[\"message\"][\"from\"]:\n            user = response[\"message\"][\"from\"][\"username\"]\n\n        elif \"first_name\" in response[\"message\"][\"from\"]:\n            user = response[\"message\"][\"from\"][\"first_name\"]\n        user_id = response[\"message\"][\"from\"][\"id\"]\n\n        return user, user_id\n\n    def handle_user_permission(self, response, user):\n        \"\"\"Handle user permissions.\n\n        This will check if the user that tried to talk with\n        the bot is allowed to do so. It will also work with\n        userid to improve security.\n\n        \"\"\"\n        user_id = response[\"message\"][\"from\"][\"id\"]\n\n        if (\n            not self.whitelisted_users\n            or user in self.whitelisted_users\n            or user_id in self.whitelisted_users\n        ):\n            return True\n\n        return False\n\n    def build_url(self, method):\n        \"\"\"Build the url to connect to the API.\n\n        Args:\n            method (string): API call end point.\n\n        Return:\n            String that represents the full API url.\n\n        \"\"\"\n        return \"https://api.telegram.org/bot{}/{}\".format(self.token, method)\n\n    async def delete_webhook(self):\n        \"\"\"Delete Telegram webhook.\n\n        The Telegram api will thrown an 409 error when an webhook is\n        active and a call to getUpdates is made. This method will\n        try to request the deletion of the webhook to make the getUpdate\n        request possible.\n\n        \"\"\"\n        _LOGGER.debug(_(\"Sending deleteWebhook request to Telegram...\"))\n        resp = await self.session.get(self.build_url(\"deleteWebhook\"))\n\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Telegram webhook deleted successfully.\"))\n        else:\n            _LOGGER.debug(_(\"Unable to delete webhook.\"))\n\n    async def connect(self):\n        \"\"\"Connect to Telegram.\n\n        This method is not an authorization call. It basically\n        checks if the API token was provided and makes an API\n        call to Telegram and evaluates the status of the call.\n\n        \"\"\"\n\n        _LOGGER.debug(_(\"Connecting to Telegram.\"))\n        self.session = aiohttp.ClientSession()\n\n        resp = await self.session.get(self.build_url(\"getMe\"))\n\n        if resp.status != 200:\n            _LOGGER.error(_(\"Unable to connect.\"))\n            _LOGGER.error(_(\"Telegram error %s, %s.\"), resp.status, resp.text)\n        else:\n            json = await resp.json()\n            _LOGGER.debug(json)\n            _LOGGER.debug(_(\"Connected to Telegram as %s.\"), json[\"result\"][\"username\"])\n\n    async def _parse_message(self, response):\n        \"\"\"Handle logic to parse a received message.\n\n        Since everyone can send a private message to any user/bot\n        in Telegram, this method allows to set a list of whitelisted\n        users that can interact with the bot. If any other user tries\n        to interact with the bot the command is not parsed and instead\n        the bot will inform that user that he is not allowed to talk\n        with the bot.\n\n        We also set self.latest_update to +1 in order to get the next\n        available message (or an empty {} if no message has been received\n        yet) with the method self._get_messages().\n\n        Args:\n            response (dict): Response returned by aiohttp.ClientSession.\n\n        \"\"\"\n        for result in response[\"result\"]:\n            _LOGGER.debug(result)\n\n            if result.get(\"edited_message\", None):\n                result[\"message\"] = result.pop(\"edited_message\")\n            if result.get(\"channel_post\", None) or result.get(\n                \"edited_channel_post\", None\n            ):\n                self.latest_update = result[\"update_id\"] + 1\n                _LOGGER.debug(\n                    _(\"Channel message parsing not supported \" \"- Ignoring message.\")\n                )\n            elif \"message\" in result and \"text\" in result[\"message\"]:\n                user, user_id = self.get_user(result)\n                message = Message(\n                    text=result[\"message\"][\"text\"],\n                    user=user,\n                    user_id=user_id,\n                    target=result[\"message\"][\"chat\"][\"id\"],\n                    connector=self,\n                )\n\n                if self.handle_user_permission(result, user):\n                    await self.opsdroid.parse(message)\n                else:\n                    message.text = (\n                        \"Sorry, you're not allowed \" \"to speak with this bot.\"\n                    )\n                    await self.send(message)\n                self.latest_update = result[\"update_id\"] + 1\n            elif (\n                \"message\" in result\n                and \"sticker\" in result[\"message\"]\n                and \"emoji\" in result[\"message\"][\"sticker\"]\n            ):\n                self.latest_update = result[\"update_id\"] + 1\n                _LOGGER.debug(\n                    _(\"Emoji message parsing not supported - Ignoring message.\")\n                )\n            else:\n                _LOGGER.error(_(\"Unable to parse the message.\"))\n\n    async def _get_messages(self):\n        \"\"\"Connect to the Telegram API.\n\n        Uses an aiohttp ClientSession to connect to Telegram API\n        and get the latest messages from the chat service.\n\n        The data[\"offset\"] is used to consume every new message, the API\n        returns an  int - \"update_id\" value. In order to get the next\n        message this value needs to be increased by 1 the next time\n        the API is called. If no new messages exists the API will just\n        return an empty {}.\n\n        \"\"\"\n        data = {}\n        if self.latest_update is not None:\n            data[\"offset\"] = self.latest_update\n\n        await asyncio.sleep(self.update_interval)\n        resp = await self.session.get(self.build_url(\"getUpdates\"), params=data)\n\n        if resp.status == 409:\n            _LOGGER.info(\n                _(\n                    \"Can't get updates because previous webhook is still active. Will try to delete webhook.\"\n                )\n            )\n            await self.delete_webhook()\n\n        if resp.status != 200:\n            _LOGGER.error(_(\"Telegram error %s, %s.\"), resp.status, resp.text)\n            self.listening = False\n        else:\n            json = await resp.json()\n\n            await self._parse_message(json)\n\n    async def get_messages_loop(self):\n        \"\"\"Listen for and parse new messages.\n\n        The bot will always listen to all opened chat windows,\n        as long as opsdroid is running. Since anyone can start\n        a new chat with the bot is recommended that a list of\n        users to be whitelisted be provided in config.yaml.\n\n        The method will sleep asynchronously at the end of\n        every loop. The time can either be specified in the\n        config.yaml with the param update-interval - this\n        defaults to 1 second.\n\n        \"\"\"\n        while self.listening:\n            await self._get_messages()\n\n    async def listen(self):\n        \"\"\"Listen method of the connector.\n\n        Every connector has to implement the listen method. When an\n        infinite loop is running, it becomes hard to cancel this task.\n        So we are creating a task and set it on a variable so we can\n        cancel the task.\n\n        \"\"\"\n        message_getter = self.loop.create_task(await self.get_messages_loop())\n        await self._closing.wait()\n        message_getter.cancel()\n\n    @register_event(Message)\n    async def send_message(self, message):\n        \"\"\"Respond with a message.\n\n        Args:\n            message (object): An instance of Message.\n\n        \"\"\"\n        _LOGGER.debug(\n            _(\"Responding with: '%s' at target: '%s'\"), message.text, message.target\n        )\n\n        data = dict()\n        data[\"chat_id\"] = message.target\n        data[\"text\"] = message.text\n        resp = await self.session.post(self.build_url(\"sendMessage\"), data=data)\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Successfully responded.\"))\n        else:\n            _LOGGER.error(_(\"Unable to respond.\"))\n\n    @register_event(Image)\n    async def send_image(self, file_event):\n        \"\"\"Send Image to Telegram.\n\n        Gets the chat id from the channel and then\n        sends the bytes of the image as multipart/form-data.\n\n        \"\"\"\n        data = aiohttp.FormData()\n        data.add_field(\n            \"chat_id\", str(file_event.target[\"id\"]), content_type=\"multipart/form-data\"\n        )\n        data.add_field(\n            \"photo\",\n            await file_event.get_file_bytes(),\n            content_type=\"multipart/form-data\",\n        )\n\n        resp = await self.session.post(self.build_url(\"sendPhoto\"), data=data)\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Sent %s image successfully.\"), file_event.name)\n        else:\n            _LOGGER.debug(_(\"Unable to send image - Status Code %s.\"), resp.status)\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Telegram.\n\n        Stops the infinite loop found in self._listen(), closes\n        aiohttp session.\n\n        \"\"\"\n        self.listening = False\n        self._closing.set()\n        await self.session.close()\n", "path": "opsdroid/connector/telegram/__init__.py"}], "after_files": [{"content": "\"\"\"A connector for Telegram.\"\"\"\nimport asyncio\nimport logging\nimport aiohttp\nfrom voluptuous import Required\n\nfrom opsdroid.connector import Connector, register_event\nfrom opsdroid.events import Message, Image, File\n\n\n_LOGGER = logging.getLogger(__name__)\nCONFIG_SCHEMA = {\n    Required(\"token\"): str,\n    \"update-interval\": float,\n    \"default-user\": str,\n    \"whitelisted-users\": list,\n}\n\n\nclass ConnectorTelegram(Connector):\n    \"\"\"A connector the the char service Telegram.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Create the connector.\n\n        Args:\n            config (dict): configuration settings from the\n                file config.yaml.\n            opsdroid (OpsDroid): An instance of opsdroid.core.\n\n        \"\"\"\n        _LOGGER.debug(_(\"Loaded Telegram Connector\"))\n        super().__init__(config, opsdroid=opsdroid)\n        self.name = \"telegram\"\n        self.opsdroid = opsdroid\n        self.latest_update = None\n        self.listening = True\n        self.default_user = config.get(\"default-user\", None)\n        self.default_target = self.default_user\n        self.whitelisted_users = config.get(\"whitelisted-users\", None)\n        self.update_interval = config.get(\"update-interval\", 1)\n        self.session = None\n        self._closing = asyncio.Event()\n        self.loop = asyncio.get_event_loop()\n\n        try:\n            self.token = config[\"token\"]\n        except (KeyError, AttributeError):\n            _LOGGER.error(\n                _(\n                    \"Unable to login: Access token is missing. Telegram connector will be unavailable.\"\n                )\n            )\n\n    @staticmethod\n    def get_user(response):\n        \"\"\"Get user from response.\n\n        The API response is different depending on how\n        the bot is set up and where the message is coming\n        from. This method was created to keep if/else\n        statements to a minium on  _parse_message.\n\n        Args:\n            response (dict): Response returned by aiohttp.ClientSession.\n\n        \"\"\"\n        user = None\n        user_id = None\n\n        if \"username\" in response[\"message\"][\"from\"]:\n            user = response[\"message\"][\"from\"][\"username\"]\n\n        elif \"first_name\" in response[\"message\"][\"from\"]:\n            user = response[\"message\"][\"from\"][\"first_name\"]\n        user_id = response[\"message\"][\"from\"][\"id\"]\n\n        return user, user_id\n\n    def handle_user_permission(self, response, user):\n        \"\"\"Handle user permissions.\n\n        This will check if the user that tried to talk with\n        the bot is allowed to do so. It will also work with\n        userid to improve security.\n\n        \"\"\"\n        user_id = response[\"message\"][\"from\"][\"id\"]\n\n        if (\n            not self.whitelisted_users\n            or user in self.whitelisted_users\n            or user_id in self.whitelisted_users\n        ):\n            return True\n\n        return False\n\n    def build_url(self, method):\n        \"\"\"Build the url to connect to the API.\n\n        Args:\n            method (string): API call end point.\n\n        Return:\n            String that represents the full API url.\n\n        \"\"\"\n        return \"https://api.telegram.org/bot{}/{}\".format(self.token, method)\n\n    async def delete_webhook(self):\n        \"\"\"Delete Telegram webhook.\n\n        The Telegram api will thrown an 409 error when an webhook is\n        active and a call to getUpdates is made. This method will\n        try to request the deletion of the webhook to make the getUpdate\n        request possible.\n\n        \"\"\"\n        _LOGGER.debug(_(\"Sending deleteWebhook request to Telegram...\"))\n        resp = await self.session.get(self.build_url(\"deleteWebhook\"))\n\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Telegram webhook deleted successfully.\"))\n        else:\n            _LOGGER.debug(_(\"Unable to delete webhook.\"))\n\n    async def connect(self):\n        \"\"\"Connect to Telegram.\n\n        This method is not an authorization call. It basically\n        checks if the API token was provided and makes an API\n        call to Telegram and evaluates the status of the call.\n\n        \"\"\"\n\n        _LOGGER.debug(_(\"Connecting to Telegram.\"))\n        self.session = aiohttp.ClientSession()\n\n        resp = await self.session.get(self.build_url(\"getMe\"))\n\n        if resp.status != 200:\n            _LOGGER.error(_(\"Unable to connect.\"))\n            _LOGGER.error(_(\"Telegram error %s, %s.\"), resp.status, resp.text)\n        else:\n            json = await resp.json()\n            _LOGGER.debug(json)\n            _LOGGER.debug(_(\"Connected to Telegram as %s.\"), json[\"result\"][\"username\"])\n\n    async def _parse_message(self, response):\n        \"\"\"Handle logic to parse a received message.\n\n        Since everyone can send a private message to any user/bot\n        in Telegram, this method allows to set a list of whitelisted\n        users that can interact with the bot. If any other user tries\n        to interact with the bot the command is not parsed and instead\n        the bot will inform that user that he is not allowed to talk\n        with the bot.\n\n        We also set self.latest_update to +1 in order to get the next\n        available message (or an empty {} if no message has been received\n        yet) with the method self._get_messages().\n\n        Args:\n            response (dict): Response returned by aiohttp.ClientSession.\n\n        \"\"\"\n        for result in response[\"result\"]:\n            _LOGGER.debug(result)\n\n            if result.get(\"edited_message\", None):\n                result[\"message\"] = result.pop(\"edited_message\")\n            if result.get(\"channel_post\", None) or result.get(\n                \"edited_channel_post\", None\n            ):\n                self.latest_update = result[\"update_id\"] + 1\n                _LOGGER.debug(\n                    _(\"Channel message parsing not supported \" \"- Ignoring message.\")\n                )\n            elif \"message\" in result and \"text\" in result[\"message\"]:\n                user, user_id = self.get_user(result)\n                message = Message(\n                    text=result[\"message\"][\"text\"],\n                    user=user,\n                    user_id=user_id,\n                    target=result[\"message\"][\"chat\"][\"id\"],\n                    connector=self,\n                )\n\n                if self.handle_user_permission(result, user):\n                    await self.opsdroid.parse(message)\n                else:\n                    message.text = (\n                        \"Sorry, you're not allowed \" \"to speak with this bot.\"\n                    )\n                    await self.send(message)\n                self.latest_update = result[\"update_id\"] + 1\n            elif (\n                \"message\" in result\n                and \"sticker\" in result[\"message\"]\n                and \"emoji\" in result[\"message\"][\"sticker\"]\n            ):\n                self.latest_update = result[\"update_id\"] + 1\n                _LOGGER.debug(\n                    _(\"Emoji message parsing not supported - Ignoring message.\")\n                )\n            else:\n                _LOGGER.error(_(\"Unable to parse the message.\"))\n\n    async def _get_messages(self):\n        \"\"\"Connect to the Telegram API.\n\n        Uses an aiohttp ClientSession to connect to Telegram API\n        and get the latest messages from the chat service.\n\n        The data[\"offset\"] is used to consume every new message, the API\n        returns an  int - \"update_id\" value. In order to get the next\n        message this value needs to be increased by 1 the next time\n        the API is called. If no new messages exists the API will just\n        return an empty {}.\n\n        \"\"\"\n        data = {}\n        if self.latest_update is not None:\n            data[\"offset\"] = self.latest_update\n\n        await asyncio.sleep(self.update_interval)\n        resp = await self.session.get(self.build_url(\"getUpdates\"), params=data)\n\n        if resp.status == 409:\n            _LOGGER.info(\n                _(\n                    \"Can't get updates because previous webhook is still active. Will try to delete webhook.\"\n                )\n            )\n            await self.delete_webhook()\n\n        if resp.status != 200:\n            _LOGGER.error(_(\"Telegram error %s, %s.\"), resp.status, resp.text)\n            self.listening = False\n        else:\n            json = await resp.json()\n\n            await self._parse_message(json)\n\n    async def get_messages_loop(self):\n        \"\"\"Listen for and parse new messages.\n\n        The bot will always listen to all opened chat windows,\n        as long as opsdroid is running. Since anyone can start\n        a new chat with the bot is recommended that a list of\n        users to be whitelisted be provided in config.yaml.\n\n        The method will sleep asynchronously at the end of\n        every loop. The time can either be specified in the\n        config.yaml with the param update-interval - this\n        defaults to 1 second.\n\n        \"\"\"\n        while self.listening:\n            await self._get_messages()\n\n    async def listen(self):\n        \"\"\"Listen method of the connector.\n\n        Every connector has to implement the listen method. When an\n        infinite loop is running, it becomes hard to cancel this task.\n        So we are creating a task and set it on a variable so we can\n        cancel the task.\n\n        \"\"\"\n        message_getter = self.loop.create_task(await self.get_messages_loop())\n        await self._closing.wait()\n        message_getter.cancel()\n\n    @register_event(Message)\n    async def send_message(self, message):\n        \"\"\"Respond with a message.\n\n        Args:\n            message (object): An instance of Message.\n\n        \"\"\"\n        _LOGGER.debug(\n            _(\"Responding with: '%s' at target: '%s'\"), message.text, message.target\n        )\n\n        data = dict()\n        data[\"chat_id\"] = message.target\n        data[\"text\"] = message.text\n        resp = await self.session.post(self.build_url(\"sendMessage\"), data=data)\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Successfully responded.\"))\n        else:\n            _LOGGER.error(_(\"Unable to respond.\"))\n\n    @register_event(Image)\n    async def send_image(self, file_event):\n        \"\"\"Send Image to Telegram.\n\n        Gets the chat id from the channel and then\n        sends the bytes of the image as multipart/form-data.\n\n        \"\"\"\n        data = aiohttp.FormData()\n        data.add_field(\n            \"chat_id\", str(file_event.target[\"id\"]), content_type=\"multipart/form-data\"\n        )\n        data.add_field(\n            \"photo\",\n            await file_event.get_file_bytes(),\n            content_type=\"multipart/form-data\",\n        )\n\n        resp = await self.session.post(self.build_url(\"sendPhoto\"), data=data)\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Sent %s image successfully.\"), file_event.name)\n        else:\n            _LOGGER.debug(_(\"Unable to send image - Status Code %s.\"), resp.status)\n\n    @register_event(File)\n    async def send_file(self, file_event):\n        \"\"\"Send File to Telegram.\n\n        Gets the chat id from the channel and then\n        sends the bytes of the file as multipart/form-data.\n\n        \"\"\"\n        data = aiohttp.FormData()\n        data.add_field(\n            \"chat_id\", str(file_event.target[\"id\"]), content_type=\"multipart/form-data\"\n        )\n        data.add_field(\n            \"document\",\n            await file_event.get_file_bytes(),\n            content_type=\"multipart/form-data\",\n        )\n\n        resp = await self.session.post(self.build_url(\"sendDocument\"), data=data)\n        if resp.status == 200:\n            _LOGGER.debug(_(\"Sent %s file successfully.\"), file_event.name)\n        else:\n            _LOGGER.debug(_(\"Unable to send file - Status Code %s.\"), resp.status)\n\n    async def disconnect(self):\n        \"\"\"Disconnect from Telegram.\n\n        Stops the infinite loop found in self._listen(), closes\n        aiohttp session.\n\n        \"\"\"\n        self.listening = False\n        self._closing.set()\n        await self.session.close()\n", "path": "opsdroid/connector/telegram/__init__.py"}]}