-

   rss_rss_hh_new

 - e-mail

 

 -

 LiveInternet.ru:
: 17.03.2011
:
:
: 51

:


[ ] Tornado + Telegram

, 15 2017 . 10:05 +
, -, , .

image
.1 UML


Telegram, , WebHook.


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

telegram.

:




, , .

, BotFather /newbot . , BotFather .

chat_id, , .
telegram , /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

CLI postgres:

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 chat

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

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 :




Html :




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("

" + txt + "

"); } // function managerResponse(txt) { $("#messages__box").append("

" + txt + "

"); }


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

psql postgres , .

tornado supervisor .

supervisor:

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


!

, supervisor, /var/log/supervisord/ , , supervisor 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.

!

, feedback.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335660/

:  

: [1] []
 

:
: 

: ( )

:

  URL