TelegramのトランスポートボットJabber会議





良い一日。



ある晴れた日、大きな休憩の後、運命は再び私をジャバー会議に追いやった。 確かに、知人の間でジャバーを使用する人はいません。2007年は忘却に沈み、テレグラムがコミュニケーションの主要な手段になりました。 モバイルデバイスでのXMPPのサポートは望まれていませんでした-Androidクライアントは、iOSとWPを使用することで、実際にはそうではありません。 また、プロトコル機能も自律性に影響します。 そのため、会議からのメッセージをTelegramチャットにブロードキャストするボットを作成しないという考えが生まれました。



ツールが使用されたとき:





主な機能と依存関係



完成した実装のうち、 jabbergramのみが見つかりましたが、1人のユーザーのみと作業できます。 Goには実務経験のない実装がまだあるため、このオプションは考慮されておらず、機能については何も言えません。



ライブラリの選択は、主にasyncioを使用するかどうかによって決まります。



最初に、1人のユーザー用のtet-a-tetダイアログを使用してバージョンが開発されました。これは後で、グループチャット用のXMPPコンポーネントを使用して、参加者ごとに個別のxmppユーザーを使用して拡張されました。



ボットは、他のユーザーとのチャットに追加できないように構成されているため、普遍的な実装と見なすことはできません。



なぜこれが行われるのですか? ボットAPIは、短時間で着信/発信リクエストの数を非常に制限し、かなり集中的なメッセージ交換でエラーが発生します。



一般的には何ですか:





ただし、2つのバージョンには違いがあります。





開発時には、仮想環境を使用すると便利です。そのため、仮想環境を作成できます。



$ python3.5 -m venv venv $ . venv/bin/activate
      
      





使用するには、pip aiohttp、slixmpp、ujsonからインストールする必要があります。 必要に応じて、gunicornを追加できます。 環境の有無にかかわらず、すべてのパッケージはPyPIにあります:



 $ pip3 install aiohttp slixmpp ujson
      
      





投稿の最後に、 bitbucketソースリポジトリへのリンクがあります。



電報の歴史



まず、Telegram APIの既製のフレームワークがいくつかの理由で使用されなかったことに注目する価値があります。





そのため、メインオブジェクトとボットAPIメソッドに対して単純なラッパーが作成され、リクエストはリクエストを使用して送信され、jsonはujsonによって解析されます。



ボットは、構成スクリプトを使用して構成されます。



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" #        xmpp  UPLOADER_URL = "example.com/upload" #   #     XMPP_JID/XMPP_PASS/XMPP_NICK     : # TG_INVITE_URL = "https://telegram.me/joinchat/ABCDefGHblahblah" #     # COMPONENT_JID = "tg.xmpp.domain.tld" # COMPONENT_PASS = "password" # XMPP_HOST = "xmpp.domain.tld" # XMPP_PORT = 5347
      
      









オブジェクトの表現は次のようになります。



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')
      
      









クエリを実行するためのボットクラス:

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 ...
      
      









すべてのリクエストは、アドレスTG_WH_URLに到達するWebhookを使用して処理されます。

RequestHandler.handle()-aiohttpリクエストを処理するコルーチン。



handler.py
 from aiohttp import web import asyncio import tgworker as tg #     bots api import mucbot as mb #    xmpp import tinyorm as orm #    sqlite3 class RequestHandler(object): ... async def handle(self, request): r = await request.text() try: ... update = tg.Update(ujson.loads(r)) log.debug("TG Update object: {}".format(ujson.loads(r))) ... except: log.error("Unexpected error: {}".format(sys.exc_info())) ... raise finally: return web.Response(status=200) def __init__(self, db: orm.TableMapper, mucBot: mb.MUCBot, tgBot: tg.Bot, tgChatId, loop): self.__db = db self.__tg = tgBot self.__mb = mucBot self.__chat_id = tgChatId self.__loop = loop ... ... loop = asyncio.get_event_loop() whHandler = RequestHandler(db, mucBot, tgBot, TG_CHAT_ID, loop) app = web.Application(loop=loop) app.router.add_route('POST', '/', whHandler.handle) ...
      
      









処理中に、テキストメッセージが会議に送信されます。 プライベートメッセージに対する応答の場合はプライベートメッセージとして、または応答時に/ pmコマンドが追加されます。



ファイルは送信前にサードパーティのサーバーにアップロードされ、ファイルへのリンクが会議に送信されます。 ほとんどの場合、このアプローチは一般的な使用には適していないため、ImgurまたはAPIを提供する別のサービスにアップロードする必要があります。 現在、ファイルは単にjTalkサーバーに送信されます。 もちろん、開発者の許可を得て。 ただし、個人用であるため、アドレスは設定で削除されます。



ステッカーは、単に絵文字表現に置き換えられます。



xmppについてのOpus



一度に2つの非常に人気のあるPythonのライブラリがありました-SleekXMPPとxmpppyです。 2番目は既に非推奨であり、サポートされていません。SleekXMPP非同期はスレッドによって実装されます。 asyncioをサポートするライブラリには、 aioxmppslixmppがあります。



Aioxmppはまだ非常に未加工であり、包括的なドキュメントはありません。 ただし、ボットの最初のバージョンはaioxmppを使用していましたが、後でslixmpp用に書き直されました。



Slixmppはasyncio上のSleekXMPPであり、インターフェースはそれぞれ同じであり、ほとんどのプラグインが動作します。 Poezioコンソールジャバークライアントで使用されます。

さらに、slixmppはライブラリの問題を解決するのに役立つ優れたサポートを提供しています。



シングルユーザーバージョンではslixmpp.ClientXMPPを基本クラスとして使用し、マルチユーザーバージョンでは-slixmpp.ComponentXMPPを使用します



XMPPイベントハンドラは次のようになります。

mucbot.py
 import slixmpp as sx class MUCBot(sx.ClientXMPP): # class MUCBot(sx.ComponentXMPP): #     ... # # Event handlers # def _sessionStart(self, event): self.get_roster() self.send_presence(ptype='available') self.plugin['xep_0045'].joinMUC(self.__mucjid, self.__nick, wait=True) #        ... # # Message handler # def _message(self, msg: sx.Message): log.debug("Got message: {}".format(str(msg).replace('\n', ' '))) ... # # Presence handler # def _presence(self, presence: sx.Presence): log.debug("Got Presence {}".format(str(presence).replace('\n', ' '))) ... # # Initialization # def __init__(self, db, tgBot, tgChatId, jid, password, mucjid, nick): super().__init__(jid, password) self.__jid = sx.JID(jid) self.__mucjid = sx.JID(mucjid) self.__nick = nick self.__tg = tgBot self.__db = db self.__chat_id = tgChatId ... #     XEP self.register_plugin('xep_XXXX') # Service Discovery ... #    xmlstream self.add_event_handler("session_start", self._sessionStart) self.add_event_handler("message", self._message) self.add_event_handler("muc::{}::presence".format(mucjid), self._presence) ...
      
      









明らかに、MUCのXEP-0045、pingのXEP-0199、およびXEP-0092を接続することは必須です。



xmppからのメッセージは、構成からのTG_CHAT_IDを使用して、ユーザー(またはグループチャット)からチャットに単に送信されます。



コンポーネントを使用するためのXMPPサーバーの構成



興味深い機能は、xmppコンポーネントを使用してユーザーを動的に作成することです。 この場合、ユーザーごとに個別のオブジェクトを作成し、認証用のデータを保存する必要はありません。 欠点は、メインアカウントを使用できないことです。



簡単で単純な理由から、Prosodyがxmppサーバーとして選択されました。



構成については説明しませんが、テンプレートとの唯一の違いは、コンポーネント(ボット構成のCOMPONENT_JID)を含めることです。



 Component "tg.xmpp.domain.tld" component_secret = "password"
      
      







韻律構成



一般的に、これは全体のxmppセットアップです。 韻律を再起動するだけです。



gunicornとnginxの物語



幸運にもnginxを見ることができた場合は、サーバーセクションにディレクティブを追加する必要があります。



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:/; }
      
      









HTTPS構成を説明する価値はないと思いますが、証明書はletsencryptを介して取得されました



例の構成はこのコメントから取られました 。 完全な構成はここで表示できます 。暗号化オプションはMozilla SSL Generatorで選択されました



このスティック全体の構築 、Debian 8.5のVPSで動作するため、gunicornを実行するsystemd用のサービスが記述されています。



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











もちろん、systemctl daemon-reloadとsystemctl enable botがジョブを実行します。



ソースリンク







PS賞の年の最も美しいコードを主張しません。 もちろん、うまくやりたかったのですが、いつものように判明しました。

PPSグループチャット用のバージョンの開発は、Telegram APIの欲求、時間、および多くの問題のために中止されました。



All Articles