Tornado + Telegramでチャットを作成および展開するための詳細ガイド

このソリューションは小規模なプロジェクトに適しています。複数のユーザーとの対話を同時に実行する機能は、新しいチャットボットを作成することで実現されます。



チャット内部デバイス
画像

図1 UMLシーケンス図



実装



実装中、WebHookを使用する代わりに、展開を簡素化し、資料の理解を容易にするために、Telegramサーバーを定期的にポーリングすることが決定されました。



仮想環境の準備



virtualenvがない場合は、インストールする必要があります。



pip install virtualenv







仮想環境を作成します。



virtualenv --no-site-packages -p python3.4 chat







有効化する:



source chat/bin/activate







チャットが機能するために必要なすべてのライブラリをインストールします。



pip install tornado==4.4.2 psycopg2==2.7.3 pyTelegramBotAPI==2.2.3







サーバーをポーリングするには、 ライブラリを使用して電報を処理します。



次のファイル構造を作成する必要があります。







ボットの作成



ボットを作成する時が来ました。この実装は、複数のボットが複数のクライアントと並行して通信できるように設計されています。



ボットを登録するには、 BotFather / newbotを記述する必要があり、それとのダイアログですべての詳細な指示を受け取ります。 その結果、登録が成功すると、BotFatherは新しいボットのトークンを返します。



次に、ボットがメッセージの送信先を認識できるように、chat_idを取得する必要があります。

これを行うには、電報アプリケーションでボットを見つけ、/ startコマンドでやり取りを開始し、メッセージを書き込んでリンクをたどります-



https://api.telegram.org/bot<__>/getUpdates







おおよそ次の答えが表示されます-



{"id":555455667,"first_name":"","last_name":"","username":"kamrus","language_code":"ru-RU"}

id chat_id








Postgresのセットアップ



チャットの作業とその近代化の可能性に柔軟性を提供するには、データベースを使用する必要があります。私はpostgresを選択しました。



postgresユーザーに切り替えます。



sudo su - postgres







postgres CLIに入ります。



psql







新しいUnicodeエンコードデータベースを作成する必要があります。



 CREATE DATABASE habr_chat ENCODING 'UNICODE';
      
      





データベースに新しいユーザーを作成します。



 CREATE USER habr_user WITH PASSWORD '12345';
      
      





そして、私たちは彼に基地に対するすべての特権を与えます:



 GRANT ALL PRIVILEGES ON DATABASE habr_chat TO habr_user;
      
      





作成したばかりのデータベースに接続しています:



\c habr_chat







ボットに情報を保存するためのテーブルを作成しましょう。次のモデルがあります。



物理モデル


図2チャットテーブルの物理モデル



 CREATE TABLE chat ( id SERIAL NOT NULL PRIMARY KEY, token character varying(300) NOT NULL UNIQUE, ready BOOLEAN NOT NULL DEFAULT True, last_message TEXT, customer_asked BOOLEAN NOT NULL DEFAULT False, remote_ip character varying(100) )
      
      





また、ユーザーにテーブルに対するすべての特権を与えます。



 GRANT ALL PRIVILEGES ON TABLE chat TO habr_user;
      
      





次に、ボットトークンを追加する必要があります。



 INSERT INTO chat (token) VALUES ('your_bot_token');
      
      





CLIの終了:



\q







ユーザーを元に戻します:



exit







コード記述



まず、チャットの設定を別のファイルで行うようにします。



bot_settings.py



 CHAT_ID =   chat_id db = { 'db_name': 'habr_chat', 'user': 'habr_user', 'password': '12345', 'host': '', 'port': '' }
      
      





主な機能はcore.pyファイルにあります



 from telebot import apihelper from bot_settings import db import psycopg2 import datetime def get_updates(token, conn, cur, offset=None, limit=None, timeout=20): '''     ''' json_updates = apihelper.get_updates(token, offset, limit, timeout) try: answer = json_updates[-1]['message']['text'] except IndexError: answer = '' #        ,  #        , #         if is_customer_asked(conn, cur, token): #    ,      #             if not is_last_message(conn, cur, token, answer): #          #    update_last_message(conn, cur, token, answer) return answer else: #      ,      #   ,      , #       update_last_message(conn, cur, token, answer) def send_message(token, chat_id, text): '''    ''' apihelper.send_message(token, chat_id, text) def connect_postgres(**kwargs): try: conn = psycopg2.connect(dbname=db['db_name'], user=db['user'], password=db['password'], host=db['host'], port=db['port']) except Exception as e: print(e, '    posqgres') raise e cur = conn.cursor() return conn, cur def update_last_message(conn, cur, token, message, **kwargs): '''   ,   ''' query = "UPDATE chat SET last_message = %s WHERE token = %s" data = [message, token] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '       %s' %message) raise e def add_remote_ip(conn, cur, token, ip): '''   ip   ''' query = "UPDATE chat SET remote_ip = %s WHERE token = %s" data = [ip, token] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '    ip ') raise e def delete_remote_ip(conn, cur, token): '''  ip       ''' query = "UPDATE chat SET remote_ip = %s WHERE token = %s" data = ['', token] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '    ip ') raise e def is_last_message(conn, cur, token, message, **kwargs): '''         ''' query = "SELECT last_message FROM chat WHERE token = %s" data = [token, ] try: cur.execute(query, data) last_message = cur.fetchone() if last_message: if last_message[0] == message: return True return False except Exception as e: print(e, '    ') raise e def update_customer_asked(conn, cur, token, to_value): '''     ''' query = "UPDATE chat SET customer_asked = %s WHERE token = %s" # to_value = True/False data = [to_value, token] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '    "customer_asked"  %s' %to_value) raise e def is_customer_asked(conn, cur, token): '''     ,    True ''' query = "SELECT customer_asked FROM chat WHERE token = %s" data = [token, ] try: cur.execute(query, data) customer_asked = cur.fetchone() return customer_asked[0] except Exception as e: print(e, "          ") raise e def get_bot(conn, cur): '''      ,   ready = True.  (id, token, ready, last_message, customer_asked)    ''' query = "SELECT * FROM chat WHERE ready = True" try: cur.execute(query) bot = cur.fetchone() if bot: return bot else: return None except Exception as e: print(e, "     ") raise e def make_bot_busy(conn, cur, token): '''   ready  False,      ''' query = "UPDATE chat SET ready = False WHERE token = %s" data = [token,] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '     "ready"  False') raise e def make_bot_free(conn, cur, token): '''   ready  False,      ''' update_customer_asked(conn, cur, token, False) delete_remote_ip(conn, cur, token) query = "UPDATE chat SET ready = True WHERE token = %s" data = [token,] try: cur.execute(query, data) conn.commit() except Exception as e: print(e, '     "ready"  True') raise e
      
      





tornadino.py



 import tornado.ioloop import tornado.web import tornado.websocket import core from bot_settings import CHAT_ID import datetime class WSHandler(tornado.websocket.WebSocketHandler): def __init__(self, application, request, **kwargs): super(WSHandler, self).__init__(application, request, **kwargs) #         postgres self.conn, self.cur = core.connect_postgres() self.get_bot(self.conn, self.cur, request.remote_ip) def get_bot(self, conn, cur, ip): while True: bot = core.get_bot(conn, cur) if bot: self.bot_token = bot[1] self.customer_asked = bot[4] #   core.make_bot_busy(self.conn, self.cur, self.bot_token) #   ip  core.add_remote_ip(self.conn, self.cur, self.bot_token, ip) break def check_origin(self, origin): '''       ''' return True def bot_callback(self): '''   PeriodicCallback    Telegram       ''' ans_telegram = core.get_updates(self.bot_token, self.conn, self.cur) if ans_telegram: #     ,       self.write_message(ans_telegram) def open(self): '''        ''' #    Telegram  3 self.telegram_loop = tornado.ioloop.PeriodicCallback(self.bot_callback, 3000) self.telegram_loop.start() def on_message(self, message): '''  ,      ''' if not self.customer_asked: self.customer_asked = True #    ,     core.update_customer_asked(self.conn, self.cur, self.bot_token, True) core.send_message(self.bot_token, CHAT_ID, message) def on_close(self): '''      ''' core.send_message(self.bot_token, CHAT_ID, "  ") #  PeriodicCallback self.telegram_loop.stop() #   core.make_bot_free(self.conn, self.cur, self.bot_token) # WebSocket     ws://127.0.0.1:8080/ws application = tornado.web.Application([ (r'/ws', WSHandler), ]) if __name__ == "__main__": application.listen(8080) tornado.ioloop.IOLoop.current().start()
      
      





次に、静的ファイルを作成します。

chat.html

コードを表示
 <div class="chatbox chatbox-down chatbox--empty"> <div class="chatbox__title"> <h5><a href="#">Tornado-Telegram-chat</a></h5> <button class="chatbox__title__close"> <span> <svg viewBox="0 0 12 12" width="12px" height="12px"> <line stroke="#FFFFFF" x1="11.75" y1="0.25" x2="0.25" y2="11.75"></line> <line stroke="#FFFFFF" x1="11.75" y1="11.75" x2="0.25" y2="0.25"></line> </svg> </span> </button> </div> <div id="messages__box" class="chatbox__body"> <!--         --> </div> <button id="start-ws" type="button" class="btn btn-success btn-block"> </button> <form> <textarea id="message" class="chatbox__message" placeholder=" ..."></textarea> <input id="sendmessage" type="hidden"> </form> </div>
      
      







chat.css

コードを表示
 .chatbox { position: fixed; bottom: 0; right: 30px; height: 400px; background-color: #fff; font-family: Arial, sans-serif; -webkit-transition: all 600ms cubic-bezier(0.19, 1, 0.22, 1); transition: all 600ms cubic-bezier(0.19, 1, 0.22, 1); display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; } .chatbox-down { bottom: -350px; } .chatbox--closed { bottom: -400px; } .chatbox .form-control:focus { border-color: #1f2836; } .chatbox__title, .chatbox__body { border-bottom: none; } .chatbox__title { min-height: 50px; padding-right: 10px; background-color: #1f2836; border-top-left-radius: 4px; border-top-right-radius: 4px; cursor: pointer; display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; } .chatbox__title h5 { height: 50px; margin: 0 0 0 15px; line-height: 50px; position: relative; padding-left: 20px; -webkit-flex-grow: 1; flex-grow: 1; } .chatbox__title h5 a { color: #fff; max-width: 195px; display: inline-block; text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .chatbox__title h5:before { content: ''; display: block; position: absolute; top: 50%; left: 0; width: 12px; height: 12px; background: #4CAF50; border-radius: 6px; -webkit-transform: translateY(-50%); transform: translateY(-50%); } .chatbox__title__tray, .chatbox__title__close { width: 24px; height: 24px; outline: 0; border: none; background-color: transparent; opacity: 0.5; cursor: pointer; -webkit-transition: opacity 200ms; transition: opacity 200ms; } .chatbox__title__tray:hover, .chatbox__title__close:hover { opacity: 1; } .chatbox__title__tray span { width: 12px; height: 12px; display: inline-block; border-bottom: 2px solid #fff } .chatbox__title__close svg { vertical-align: middle; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.2px; } .chatbox__body, .chatbox__credentials { padding: 15px; border-top: 0; background-color: #f5f5f5; border-left: 1px solid #ddd; border-right: 1px solid #ddd; -webkit-flex-grow: 1; flex-grow: 1; } .chatbox__credentials { display: none; } .chatbox__credentials .form-control { -webkit-box-shadow: none; box-shadow: none; } .chatbox__body { overflow-y: auto; } .chatbox__body__message { position: relative; } .chatbox__body__message p { padding: 15px; border-radius: 4px; font-size: 14px; background-color: #fff; -webkit-box-shadow: 1px 1px rgba(100, 100, 100, 0.1); box-shadow: 1px 1px rgba(100, 100, 100, 0.1); } .chatbox__body__message img { width: 40px; height: 40px; border-radius: 4px; border: 2px solid #fcfcfc; position: absolute; top: 15px; } .chatbox__body__message--left p { margin-left: 15px; padding-left: 30px; text-align: left; } .chatbox__body__message--left img { left: -5px; } .chatbox__body__message--right p { margin-right: 15px; padding-right: 30px; text-align: right; } .chatbox__body__message--right img { right: -5px; } .chatbox__message { padding: 15px; min-height: 50px; outline: 0; resize: none; border: none; font-size: 12px; border: 1px solid #ddd; border-bottom: none; background-color: #fefefe; width: 100%; } .chatbox--empty { height: 262px; } .chatbox--empty.chatbox-down { bottom: -212px; } .chatbox--empty.chatbox--closed { bottom: -262px; } .chatbox--empty .chatbox__body, .chatbox--empty .chatbox__message { display: none; } .chatbox--empty .chatbox__credentials { display: block; } .description { font-family: Arial, sans-serif; font-size: 12px; } #start-ws { margin-top: 30px; } .no-visible { display: none; }
      
      







javascriptファイルを作成する前に、クライアントとマネージャーからのメッセージのコードがどのように見えるかを決定する必要があります。



クライアントからのメッセージのHTMLコード:



コードを表示
 <div class="chatbox__body__message chatbox__body__message--right"> <img src="../static/user.png" alt=""> <p></p> </div>
      
      







マネージャーからのメッセージのHTMLコード:



コードを表示
 <div class="chatbox__body__message chatbox__body__message--right"> <img src="../static/user.png" alt=""> <p></p> </div>
      
      







chat.js

コードを表示
 (function($) { $(document).ready(function() { var $chatbox = $('.chatbox'), $chatboxTitle = $('.chatbox__title'), $chatboxTitleClose = $('.chatbox__title__close'), $chatboxWs = $('#start-ws'); //         $chatboxTitle.on('click', function() { $chatbox.toggleClass('chatbox-down'); }); //   $chatboxTitleClose.on('click', function(e) { e.stopPropagation(); $chatbox.addClass('chatbox--closed'); //       ,  //    if (window.sock) { window.sock.close(); } }); //    $chatboxWs.on('click', function(e) { e.preventDefault(); //    $chatbox.removeClass('chatbox--empty'); //      $chatboxWs.addClass('no-visible'); if (!("WebSocket" in window)) { alert("    web sockets"); } else { alert(" "); setup(); } }); }); })(jQuery); //     WebSocket function setup(){ var host = "ws://62.109.2.175:8084/ws"; var socket = new WebSocket(host); window.sock = socket; var $txt = $("#message"); var $btnSend = $("#sendmessage"); //    textarea $txt.focus(); $btnSend.on('click',function(){ var text = $txt.val(); if(text == ""){return} //     socket.send(text); //     clientRequest(text); $txt.val(""); // $('#send') }); //   enter $txt.keypress(function(evt){ //    enter if(evt.which == 13){ $btnSend.click(); } }); if(socket){ //      socket.onopen = function(){ } //        socket.onmessage = function(msg){ //     managerResponse(msg.data); } //      socket.onclose = function(){ webSocketClose("The connection has been closed."); window.sock = false; } }else{ console.log("invalid socket"); } } function webSocketClose(txt){ var p = document.createElement('p'); p.innerHTML = txt; document.getElementById('messages__box').appendChild(p); } //    function clientRequest(txt) { $("#messages__box").append("<div class='chatbox__body__message chatbox__body__message--right'> <img src='../static/user.png' alt=''> <p>" + txt + "</p> </div>"); } //     function managerResponse(txt) { $("#messages__box").append("<div class='chatbox__body__message chatbox__body__message--left'> <img src='../static/user.png' alt=''> <p>" + txt + "</p> </div>"); }
      
      







centos7での展開



まず、アプリケーションの仮想環境を構成する必要があります。実際には、実装ポイントのローカルマシンで既に行ったことを繰り返します。



環境をセットアップしたら、プロジェクトをそこに転送する必要があります。これを行う最も簡単な方法は、gitを使用することです。まずコードをリポジトリにアップロードし、そこからサーバーにクローンを作成する必要があります。



postgresをカスタマイズする



サーバーにpostgresがインストールされていない場合は、次のようにインストールできます。



sudo yum install postgresql-server postgresql-devel postgresql-contrib







postgresを実行します。



sudo postgresql-setup initdb





sudo systemctl start postgresql







自動起動を追加します。



sudo systemctl enable postgresql







次に、postgresユーザーでpsqlにアクセスし、ローカルマシンで行ったすべての操作を繰り返す必要があります。



バックグラウンドでスーパーバイザーを使用して竜巻アプリケーションを起動します。



まず、スーパーバイザーをインストールします。



sudo yum install supervisor







次に、/ etc / supervisor.confにあるスーパーバイザー構成ファイルを開きます。



[unix_http_server]

file=/path/to/supervisor.sock ; (the path to the socket file)



[supervisord]

logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)

logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)

logfile_backups=10 ; (num of main logfile rotation backups;default 10)

loglevel=error ; (log level;default info; others: debug,warn,trace)

pidfile=/path/to/supervisord.pid ; (supervisord pidfile;default supervisord.pid)

nodaemon=false ; (start in foreground if true;default false)

minfds=1024 ; (min. avail startup file descriptors;default 1024)

minprocs=200 ; (min. avail process descriptors;default 200)

user=root

childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)



[rpcinterface:supervisor]

supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface



[supervisorctl]

serverurl=unix:///path/to/supervisor.sock ; use a unix:// URL for a unix socket



[program:tornado-8004]

environment=PATH="/path/to/chat/bin"

command=/path/to/chat/bin/python3.4 /path/to/tornadino.py --port=8084

stopsignal=KILL

stderr_logfile=/var/log/supervisord/tornado-stderr.log

stdout_logfile=/var/log/supervisord/tornado-stdout.log



[include]

files = supervisord.d/*.ini








構成ファイルのパスを変更することを忘れないでください!



スーパーバイザーを開始する前に、フォルダーを作成する必要があります/ var / log / supervisord / tornadoログがその中に収集されるため、スーパーバイザーがtornado-8004を開始したがチャットが機能しない場合は、そこでエラーを探す必要があります。



スーパーバイザーを起動します。



sudo supervisorctl start tornado-8004







すべてが正常であることを確認します。



sudo supervisorctl status







このようなものを取得する必要があります:



tornado-8004 RUNNING pid 32139, uptime 0:08:10







ローカルマシンで、chat.jsを変更します。



var host = "ws://__:8084/ws";







そして、ブラウザでchat.htmlを開きます。



できた!



このようなチャットは、特別なジェスチャーなしでプロジェクトに固定できます。フィードバックを収集するために使用することも非常に便利です。



All Articles