
良い一日。
ある晴れた日、大きな休憩の後、運命は再び私をジャバー会議に追いやった。 確かに、知人の間でジャバーを使用する人はいません。2007年は忘却に沈み、テレグラムがコミュニケーションの主要な手段になりました。 モバイルデバイスでのXMPPのサポートは望まれていませんでした-Androidクライアントは、iOSとWPを使用することで、実際にはそうではありません。 また、プロトコル機能も自律性に影響します。 そのため、会議からのメッセージをTelegramチャットにブロードキャストするボットを作成しないという考えが生まれました。
ツールが使用されたとき:
- Python 3.5
- Telegram APIのaiohttp
- xmppのslixmpp
- wsgiサーバーとしてのgunicorn
- gunicornのフロントエンドおよびプロキシとしてのnginx
- IDEとしてのVSコード
主な機能と依存関係
完成した実装のうち、 jabbergramのみが見つかりましたが、1人のユーザーのみと作業できます。 Goには実務経験のない実装がまだあるため、このオプションは考慮されておらず、機能については何も言えません。
ライブラリの選択は、主にasyncioを使用するかどうかによって決まります。
最初に、1人のユーザー用のtet-a-tetダイアログを使用してバージョンが開発されました。これは後で、グループチャット用のXMPPコンポーネントを使用して、参加者ごとに個別のxmppユーザーを使用して拡張されました。
ボットは、他のユーザーとのチャットに追加できないように構成されているため、普遍的な実装と見なすことはできません。
なぜこれが行われるのですか? ボットAPIは、短時間で着信/発信リクエストの数を非常に制限し、かなり集中的なメッセージ交換でエラーが発生します。
一般的には何ですか:
- 共通ダイアログでのテキストメッセージの送信/受信
- 双方向のメッセージ編集( XEP-0308 )
- プライベートメッセージ
- 対談者のニックネームによる回答
- ファイル、音声、画像(サードパーティのサービスからダウンロード)
- ステッカー(絵文字に変更)
- 最後のメッセージ以降に非アクティブなときの自動ステータス
- 会議のニックネームを変更する
ただし、2つのバージョンには違いがあります。
- グループチャットでは、ユーザーのニックネームを持つ「強調表示」メッセージは機能しません。これは、テレグラムで個別に行うことは不可能だからです。
- ボットは、テレグラムでグループチャットをシームレスにします。つまり、参加者がxmpp会議で禁止されている場合、チャットにメッセージを書き込むことはできません。
開発時には、仮想環境を使用すると便利です。そのため、仮想環境を作成できます。
$ python3.5 -m venv venv $ . venv/bin/activate
使用するには、pip aiohttp、slixmpp、ujsonからインストールする必要があります。 必要に応じて、gunicornを追加できます。 環境の有無にかかわらず、すべてのパッケージはPyPIにあります:
$ pip3 install aiohttp slixmpp ujson
投稿の最後に、 bitbucketソースリポジトリへのリンクがあります。
電報の歴史
まず、Telegram APIの既製のフレームワークがいくつかの理由で使用されなかったことに注目する価値があります。
- Asyncioは、開始時にaiotgのみをサポートしていました。 今では誰もが人気があるようです
- Webhookは多くの場合、長いプールへの追加として実装され、いずれにしても、着信接続を処理するためにライブラリを使用する必要があります
- 一般に、多くのライブラリ機能は必要ありませんでした。
- まあ、またはちょうどNIH
そのため、メインオブジェクトとボット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をサポートするライブラリには、 aioxmppとslixmppがあります。
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で選択されました
この
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の欲求、時間、および多くの問題のために中止されました。