[ ] Tornado + Telegram |
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
https://api.telegram.org/bot<__>/getUpdates
{"id":555455667,"first_name":"","last_name":"","username":"kamrus","language_code":"ru-RU"}
id chat_id
sudo su - postgres
psql
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
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');
\q
exit
CHAT_ID = chat_id
db = {
'db_name': 'habr_chat',
'user': 'habr_user',
'password': '12345',
'host': '',
'port': ''
}
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
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()
.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;
}
(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 + "
");
}
sudo yum install postgresql-server postgresql-devel postgresql-contrib
sudo postgresql-setup initdb
sudo systemctl start postgresql
sudo systemctl enable postgresql
sudo yum install supervisor
[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
sudo supervisorctl start tornado-8004
sudo supervisorctl status
tornado-8004 RUNNING pid 32139, uptime 0:08:10
var host = "ws://__:8084/ws";