
Good day.
One fine day, after a significant break, fate again pushed me into jabber conferences. True, no one uses jabber among friends, 2007 has sunk into oblivion, Telegram has become the main means of communication. XMPP support on mobile devices left a lot to be desired - clients on Android are good every one of them, with iOS and WP, ​​to put it mildly, not very much. And features of the protocol also affect autonomy. Therefore, the thought arose: wouldn’t the bot be able to translate messages from conferences into a Telegram chat room?
')
As tools used:
Key features and dependencies
From the ready implementations, only a
jabbergram was found, but it allows you to work with only one user. There is also an implementation on Go, with which there was no experience, so this option was not considered and I can’t say anything about the functionality.
The choice of libraries is mainly due to the desire to work with asyncio.
Initially, a single-user tet-a-tet dialogue was developed, which was later expanded using
XMPP Components for group chats, with a separate xmpp-user for each participant.
The bot is configured so that it cannot be added to the chat with another user, therefore it is impossible to consider it as a universal implementation.
Why is this done? The bot API greatly limits the number of incoming / outgoing requests in a short time, and with a fairly intensive exchange of messages, errors will occur.
What is in general:
- Sending / receiving text messages in a general dialog
- Bilateral message editing ( XEP-0308 )
- Private messages
- The answer on the nickname of the interlocutor
- Files, audio, images (uploaded through a third-party service)
- Stickers (replaced by emoji)
- Autostatus when inactive since last post
- Change of nickname in the conference
However, there are differences between the two versions:
- “Illumination” of messages with the user's nickname does not work in group chats, since it is impossible to do this individually in the telegram
- A bot makes group chat in a telegram seamless, that is, if a participant is banned from an xmpp conference, he cannot write messages to the chat
When developing it is convenient to use virtual environments, so you can create one:
$ python3.5 -m venv venv $ . venv/bin/activate
To use, you need to install from pip aiohttp, slixmpp and ujson. If desired, you can add gunicorn. With or without environment, all packages are in PyPI:
$ pip3 install aiohttp slixmpp ujson
At the end of the post there are links to bitbucket repositories with source code.
Telegram history
First it should be noted that the ready-made frameworks for the Telegram API were not used for a number of reasons:
- At the time you started, asyncio supported only aiotg. Now it seems all popular
- Webbooks are often implemented as an additive to a long pool, and in any case, you have to use the library to handle incoming connections.
- In general, many library features were simply not needed.
- Well, or just NIH
So a simple wrapper was made over the main objects and methods of
bots api , requests are sent using
requests , json is parsed by
ujson , because it is faster.
Setting up the bot is done via the config-script:
config.py VERSION = "0.1" TG_WH_URL = "https://yourdomain.tld/path/123456" TG_TOKEN = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" TG_CHAT_ID = 12345678 XMPP_JID = "jid@domain.tld" XMPP_PASS = "yourpassword" XMPP_MUC = "muc@conference.domain.tld" XMPP_NICK = "nickname" DB_FILENAME = "bot.db" LOG_FILENAME = "bot.log" ISIDA_NICK = "IsidaBot"
The representation of objects looks like this:
mapping.py class User(object): def __str__(self): return '<User id={} first_name="{}" last_name="{}" username={}>'.format(self.id, self.first_name, self.last_name, self.username) def __init__(self, obj): self.id = obj.get('id') self.first_name = obj.get('first_name') self.last_name = obj.get('last_name') self.username = obj.get('username')
Bot class for querying:
bind.py class Bot(object): def _post(self, method, payload=None): r = requests.post(self.__apiUrl + method, payload).text return ujson.loads(r) ... def getMe(self): r = self._post('getMe') return User(r.get('result')) if r.get('ok') else None ... @property def token(self): return self.__token ... def __init__(self, token): self.__token = token ...
All requests are processed using webhukov, which come to the address TG_WH_URL.
RequestHandler.handle () - coroutine to handle aiohttp requests.
handler.py from aiohttp import web import asyncio import tgworker as tg
During processing, text messages are sent to the conference. Either as a private message, if it is an answer to a private message or when the response is added, the / pm command is added.
Files are uploaded to a third-party server before being sent, and a link to the file is sent to the conference. Most likely, for general use, such an approach will not work and you will have to do a download on Imgur or another service provided by the API. Now the files are simply sent to the jTalk server. With the permission of the developer, of course. But, since it is still for personal use, the address is in the config.
Stickers are simply replaced by their emoji presentation.
Opus on xmpp
At one time, python had two very popular libraries - SleekXMPP and xmpppy. The second is already outdated and not supported, and SleekXMPP asynchrony is implemented by threads. Of the libraries that support asyncio, there is
aioxmpp and
slixmpp .
Aioxmpp is still very raw and does not have comprehensive documentation. However, the first version of the bot used aioxmpp, but then rewritten for slixmpp.
Slixmpp is SleekXMPP on asyncio, the interface is the same there, respectively, most plugins will work. It is used in the console jabber client
Poezio .
In addition, slixmpp has great support, which helped solve some problems with the library.
The single-user version uses slixmpp.ClientXMPP as the base class, while the multi-user version uses slixmpp.ComponentXMPP
The XMPP event handler looks like this:
mucbot.py import slixmpp as sx class MUCBot(sx.ClientXMPP):
Obviously, it will be mandatory to connect XEP-0045 for MUC, XEP-0199 for pings and XEP-0092 will also be useful to show
everyone what we are cool about .
Messages from xmpp are simply sent to the chat from the user (or group chat) with the TG_CHAT_ID from the config.
Setting up an XMPP server to work with components
An interesting feature is the use of xmpp components to dynamically create users. You do not need to create a separate object for each user and store data for authorization. The downside is that you will not be able to use your primary account.
For reasons of ease and simplicity, Prosody was chosen as the xmpp server.
I will not describe the configuration, the only difference from the template is the inclusion of the component (COMPONENT_JID from the bot config):
Component "tg.xmpp.domain.tld" component_secret = "password"
Prosody configurationIn general, this is the whole xmpp setup. It remains only to restart prosody.
The Tale of gunicorn and nginx
If it is so coincidental that nginx is looking out by chance, you should add a directive to the server section.
nginx.cfg location /path/to/123456 { error_log /path/to/www/logs/bot_error.log; access_log /path/to/www/logs/bot_access.log; alias /path/to/www/bot/public; proxy_pass http://unix:/path/to/www/bot/bot.sock:/; }
I think it’s not worth setting the HTTPS setting, but the certificates were obtained via
letsencrypt .
The configuration for the example took from
this comment . The full config can be viewed
here , the parameters for encryption were selected in the
Mozilla SSL GeneratorThis whole construct
of ... sticks works on VPS with Debian 8.5, so for systemd, a service is written that launches gunicorn:
bot.service[Unit]
After=network.target
[Service]
PIDFile=/path/to/www/bot/bot.pid
User=service
Group=www-data
WorkingDirectory=/path/to/www/bot
ExecStart=/path/to/venv/bin/gunicorn --pid bot.pid --workers 1 --bind unix:bot.sock -m 007 bot:app --worker-class aiohttp.worker.GunicornWebWorker
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Of course, it doesn’t hurt to perform systemctl daemon-reload and systemctl enable bot.
Source links
PS For the award the most beautiful code of the year I do not pretend. Of course, I wanted to do well, but it turned out as always.PPS Development of a version for group chats is abandoned due to lack of desire, time and a number of problems with the Telegram API.