How again? Another tutorial chewing on official Telegram documentation, did you think? Yes, but no! This is more a discussion on how to build a functional bot service using
Python3.5 + ,
asyncio and
aiohttp . The more interesting that the title is actually disingenuous ...
So what is the sly headline? Firstly, the code is not 50 lines, but only 39, and secondly, the bot is not so complicated, just an echo-bot. But it seems to me that this is enough to believe that making your own bot service is not as difficult as it may seem.
Telegram-bot in 39 lines of codeimport asyncio import aiohttp from aiohttp import web import json TOKEN = '111111111:AAHKeYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' API_URL = 'https://api.telegram.org/bot%s/sendMessage' % TOKEN async def handler(request): data = await request.json() headers = { 'Content-Type': 'application/json' } message = { 'chat_id': data['message']['chat']['id'], 'text': data['message']['text'] } async with aiohttp.ClientSession(loop=loop) as session: async with session.post(API_URL, data=json.dumps(message), headers=headers) as resp: try: assert resp.status == 200 except: return web.Response(status=500) return web.Response(status=200) async def init_app(loop): app = web.Application(loop=loop, middlewares=[]) app.router.add_post('/api/v1', handler) return app if __name__ == '__main__': loop = asyncio.get_event_loop() try: app = loop.run_until_complete(init_app(loop)) web.run_app(app, host='0.0.0.0', port=23456) except Exception as e: print('Error create server: %r' % e) finally: pass loop.close()
Further, in a few words, what for and how to make the best of what is already there.
Content:
- What we use
- How to use
- What can be improved
- Real world
1. What we use
- First, Python 3.5+ . Why exactly 3.5+, because asyncio [2] and because sugar async , await etc;
- secondly, aiohttp . Since the service is on webbugs, it is both an HTTP server and an HTTP client at the same time, and what to use for this, if not aiohttp [3] ;
- thirdly, why webhook , but not long polling ? If bot-sender was not originally planned, then interactivity is its main function. I will express my opinion that for this task, the bot as an HTTP server is better suited than as a client. Yes, and give up some of the work (message delivery) to Telegram services.
And yet, you must have a domain name under control, a valid or self-signed certificate. Access to the server to which the domain name points to configure the reverse proxy to the address of the service.
To the content2. How to use
Server
The state of the
aiohttp library at the moment is such that with its use you can build a full-fledged web server in Django-style
[4] .
')
For
standalone service, all power is not useful, so server creation is limited to a few lines.
We initialize the web application:
async def init_app(loop): app = web.Application(loop=loop, middlewares=[]) app.router.add_post('/api/v1', handler) return app
NB Please note that here we define the routing and set the handler of incoming messages.
And start the web server:
app = loop.run_until_complete(init_app(loop)) web.run_app(app, host='0.0.0.0', port=23456)
Customer
To send a message, we use the
sendMessage method from the Telegram API, for this you need to send a properly prepared URL POST request with parameters as a JSON object. And we do this using aiohttp:
TOKEN = '111111111:AAHKeYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' API_URL = 'https://api.telegram.org/bot%s/sendMessage' % TOKEN ... async def handler(request): data = await request.json() headers = { 'Content-Type': 'application/json' } message = { 'chat_id': data['message']['chat']['id'], 'text': data['message']['text'] } async with aiohttp.ClientSession(loop=loop) as session: async with session.post(API_URL, data=json.dumps(message), headers=headers) as resp: try: assert resp.status == 200 except: return web.Response(status=500) return web.Response(status=200)
NB Please note that in case of successful processing of an incoming message and successful sending of an “echo”, the handler returns an empty response with the HTTP status 200. If this is not done, Telegram services will continue to “pull” hook requests for some time or will receive 200 in response, or until the time specified for the message has expired.
To the content3. What can be improved?
There is no limit to perfection, a couple of ideas on how to make service more functional.
We use middleware
Suppose there was a need to filter incoming messages. Preprocessing messages can be done on special web handlers; in terms of
aiohtttp , these are
middlewares [5] .
Example, define middleware to ignore messages from users from the blacklist:
async def middleware_factory(app, handler): async def middleware_handler(request): data = await request.json() if data['message']['from']['id'] in black_list: return web.Response(status=200) return await handler(request) return middleware_handler
And we add the handler during web application initialization:
async def init_app(loop): app = web.Application(loop=loop, middlewares=[]) app.router.add_post('/api/v1', handler) app.middlewares.append(middleware_factory) return app
Thoughts on the processing of incoming messages
If the bot is more complicated than the parrot repeater, then we can suggest the following hierarchy of
Api →
Conversation →
CustomConversation objects .
Pseudocode:
class Api(object): URL = 'https://api.telegram.org/bot%s/%s' def __init__(self, token, loop): self._token = token self._loop = loop async def _request(self, method, message): headers = { 'Content-Type': 'application/json' } async with aiohttp.ClientSession(loop=self._loop) as session: async with session.post(self.URL % (self._token, method), data=json.dumps(message), headers=headers) as resp: try: assert resp.status == 200 except: pass async def sendMessage(self, chatId, text): message = { 'chat_id': chatId, 'text': text } await self._request('sendMessage', message) class Conversation(Api): def __init__(self, token, loop): super().__init__(token, loop) async def _handler(self, message): pass async def handler(self, request): message = await request.json() asyncio.ensure_future(self._handler(message['message'])) return aiohttp.web.Response(status=200) class EchoConversation(Conversation): def __init__(self, token, loop): super().__init__(token, loop) async def _handler(self, message): await self.sendMessage(message['chat']['id'], message['text'])
Inheriting from
Conversation and redefining
_handler we get custom handlers, depending on the bot's functionality - weather, financial, etc.
And our service turns into a farm:
echobot = EchoConversation(TOKEN1, loop) weatherbot = WeatherConversation(TOKEN2, loop) finbot = FinanceConversation(TOKEN3, loop) ... app.router.add_post('/api/v1/echo', echobot.handler) app.router.add_post('/api/v1/weather', weatherbot.handler) app.router.add_post('/api/v1/finance', finbot.handler)
To the content4. The real world
Register webhook
Create
data.json :
{ "url": "https://bots.domain.tld/api/v1/echo" }
And we call the corresponding API method in any available way, for example:
curl -X POST -d @data.json -H "Content-Type: application/json" "https://api.telegram.org/botYOURBOTTOKEN/setWebhook"
NB Your domain, the hook on which you set, must be resolved, otherwise the setWebhook method will not work.
We use a proxy server
As the documentation says:
ports currently supported for Webhooks: 443, 80, 88, 8443.How to be in the case of self-hosted, when the necessary ports are probably already occupied by the web server, and we did not configure the connection via HTTPS?
The answer is simple, starting the service on any available local interface and using reverse proxies, and
nginx is better to find something else here, let it take on the task of organizing an HTTPS connection and forwarding requests to our service.
To the contentConclusion
I hope that working with the bot via web-brows did not seem much more difficult than long polling, as for me it is even easier, more flexible and more transparent. Additional costs for the organization of the server should not frighten the real bosses.
Let your ideas find a worthy tool for implementation.
Useful:
- Telegram Bot API
- 18.5. asyncio - Asynchronous I / O, event loop, coroutines and tasks
- aiohttp: Asynchronous HTTP Client / Server
- aiohttp: Server Tutorial
- aiohttp: Server Usage - Middlewares