Support for groups, stability improvements, sending TG gifs as VK videos

This commit is contained in:
Sergey 2017-07-09 22:54:33 +03:00 committed by GitHub
commit b361455e31
3 changed files with 155 additions and 50 deletions

View File

@ -1,6 +1,7 @@
# tgvkbot # tgvkbot
Бот позволяет получать и отправлять сообщения VK находясь в Telegram Бот позволяет получать и отправлять сообщения VK находясь в Telegram
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)

123
bot.py
View File

@ -9,14 +9,13 @@ import traceback
import ujson import ujson
import vk import vk
import wget import wget
import time
from PIL import Image from PIL import Image
from telebot import types from telebot import types
from credentials import token, vk_app_id from credentials import token, vk_app_id
from vk_messages import VkMessage, VkPolling from vk_messages import VkMessage, VkPolling
logging.basicConfig(format='%(levelname)-8s [%(asctime)s] %(message)s', level=logging.WARNING, filename='vk.log')
vk_threads = {} vk_threads = {}
vk_dialogs = {} vk_dialogs = {}
@ -28,6 +27,7 @@ vk_tokens = redis.from_url(os.environ.get("REDIS_URL"))
currentchat = {} currentchat = {}
bot = telebot.AsyncTeleBot(token) bot = telebot.AsyncTeleBot(token)
bot.remove_webhook()
link = 'https://oauth.vk.com/authorize?client_id={}&' \ link = 'https://oauth.vk.com/authorize?client_id={}&' \
'display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,messages,offline,docs,photos,video' \ 'display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,messages,offline,docs,photos,video' \
@ -67,6 +67,8 @@ def replace_shields(text):
def request_user_dialogs(session, userid): def request_user_dialogs(session, userid):
order = [] order = []
users_ids = [] users_ids = []
group_ids = []
positive_group_ids = []
dialogs = vk.API(session).messages.getDialogs(count=200) dialogs = vk.API(session).messages.getDialogs(count=200)
for chat in dialogs[1:]: for chat in dialogs[1:]:
if 'chat_id' in chat: if 'chat_id' in chat:
@ -77,14 +79,28 @@ def request_user_dialogs(session, userid):
elif chat['uid'] > 0: elif chat['uid'] > 0:
order.append({'title': None, 'id': chat['uid']}) order.append({'title': None, 'id': chat['uid']})
users_ids.append(chat['uid']) users_ids.append(chat['uid'])
elif chat['uid'] < 0:
order.append({'title': None, 'id': chat['uid']})
group_ids.append(chat['uid'])
for g in group_ids:
positive_group_ids.append(str(g)[1:])
users = vk.API(session).users.get(user_ids=users_ids, fields=['first_name', 'last_name', 'uid']) users = vk.API(session).users.get(user_ids=users_ids, fields=['first_name', 'last_name', 'uid'])
groups = vk.API(session).groups.getById(group_ids=positive_group_ids, fields=[])
for output in order: for output in order:
if output['title'] == ' ... ' or not output['title']: if output['title'] == ' ... ' or not output['title']:
if output['id'] > 0:
for x in users: for x in users:
if x['uid'] == output['id']: if x['uid'] == output['id']:
current_user = x output['title'] = '{} {}'.format(x['first_name'], x['last_name'])
break
else:
for f in groups:
if str(f['gid']) == str(output['id'])[1:]:
output['title'] = '{}'.format(f['name'])
break break
output['title'] = '{} {}'.format(current_user['first_name'], current_user['last_name'])
for button in range(len(order)): for button in range(len(order)):
order[button] = types.InlineKeyboardButton(order[button]['title'], callback_data=str(order[button]['id'])) order[button] = types.InlineKeyboardButton(order[button]['title'], callback_data=str(order[button]['id']))
rows = [order[x:x + 2] for x in range(0, len(order), 2)] rows = [order[x:x + 2] for x in range(0, len(order), 2)]
@ -136,8 +152,13 @@ def search_users(message, text):
def callback_buttons(call): def callback_buttons(call):
if call.message: if call.message:
if 'page' in call.data: if 'page' in call.data:
bot.answer_callback_query(call.id).wait() try:
create_markup(call.message, call.from_user.id, int(call.data.split('page')[1]), True) create_markup(call.message, call.from_user.id, int(call.data.split('page')[1]), True)
except:
session = VkMessage(vk_tokens.get(str(call.from_user.id))).session
request_user_dialogs(session, call.from_user.id)
create_markup(call.message, call.from_user.id, int(call.data.split('page')[1]), True)
bot.answer_callback_query(call.id).wait()
elif 'search' in call.data: elif 'search' in call.data:
markup = types.ForceReply(selective=False) markup = types.ForceReply(selective=False)
bot.answer_callback_query(call.id, 'Поиск беседы 🔍').wait() bot.answer_callback_query(call.id, 'Поиск беседы 🔍').wait()
@ -154,24 +175,31 @@ def callback_buttons(call):
'<i>Вы в беседе {}</i>'.format(chat['title']), '<i>Вы в беседе {}</i>'.format(chat['title']),
parse_mode='HTML').wait() parse_mode='HTML').wait()
currentchat[str(call.from_user.id)] = call.data currentchat[str(call.from_user.id)] = call.data
elif call.data.isdigit(): elif call.data.lstrip('-').isdigit():
session = VkMessage(vk_tokens.get(str(call.from_user.id))).session session = VkMessage(vk_tokens.get(str(call.from_user.id))).session
if '-' in call.data:
user = vk.API(session).groups.getById(group_id=call.data.lstrip('-'), fields=[])[0]
user = {'first_name': user['name'], 'last_name': ''}
else:
user = vk.API(session).users.get(user_ids=call.data, fields=[])[0] user = vk.API(session).users.get(user_ids=call.data, fields=[])[0]
bot.answer_callback_query(call.id, bot.answer_callback_query(call.id,
'Вы в чате с {} {}'.format(user['first_name'], user['last_name'])).wait() 'Вы в чате с {} {}'.format(user['first_name'], user['last_name'])).wait()
bot.send_message(call.from_user.id, bot.send_message(call.from_user.id,
'<i>Вы в чате с {} {}</i>'.format(user['first_name'], user['last_name']), '<i>Вы в чате с {} {}</i>'.format(user['first_name'], user['last_name']),
parse_mode='HTML').wait() parse_mode='HTML').wait()
currentchat[str(call.from_user.id)] = call.data currentchat[str(call.from_user.id)] = {'title': user['first_name'] + ' ' + user['last_name'],
'id': call.data}
def create_thread(uid, vk_token): def create_thread(uid, vk_token):
a = VkPolling() a = VkPolling()
t = threading.Thread(name='vk' + str(uid), target=a.run, args=(VkMessage(vk_token), bot, uid,)) longpoller = VkMessage(vk_token)
t = threading.Thread(name='vk' + str(uid), target=a.run, args=(longpoller, bot, uid,))
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
vk_threads[str(uid)] = a vk_threads[str(uid)] = a
vk_tokens.set(str(uid), vk_token) vk_tokens.set(str(uid), vk_token)
vk.API(longpoller.session).account.setOffline()
def check_thread(uid): def check_thread(uid):
@ -181,11 +209,29 @@ def check_thread(uid):
return True return True
# Creating VkPolling threads and dialogs info after bot reboot using existing tokens # Creating VkPolling threads and dialogs info after bot's reboot/exception using existing tokens
def thread_supervisor():
while True:
for uid in vk_tokens.scan_iter(): for uid in vk_tokens.scan_iter():
if check_thread(uid.decode("utf-8")): tries = 0
while check_thread(uid.decode("utf-8")):
if tries < 6:
try:
create_thread(uid.decode("utf-8"), vk_tokens.get(uid)) create_thread(uid.decode("utf-8"), vk_tokens.get(uid))
request_user_dialogs(VkMessage(vk_tokens.get(uid.decode("utf-8"))).session, uid.decode("utf-8")) except:
tries = tries + 1
else:
mark = types.InlineKeyboardMarkup()
login = types.InlineKeyboardButton('ВХОД', url=link)
mark.add(login)
bot.send_message(uid.decode("utf-8"), '<b>Непредвиденная ошибка, требуется повторный логин ВК!</b>',
parse_mode='HTML', reply_markup=mark)
time.sleep(60)
supervisor = threading.Thread(name='supervisor', target=thread_supervisor)
supervisor.setDaemon(True)
supervisor.start()
def stop_thread(message): def stop_thread(message):
@ -222,21 +268,17 @@ def info_extractor(info):
def chat_command(message): def chat_command(message):
if logged(message): if logged(message):
if str(message.from_user.id) in currentchat: if str(message.from_user.id) in currentchat:
if 'group' in currentchat[str(message.from_user.id)]: if 'group' in currentchat[str(message.from_user.id)]['id']:
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session chat = currentchat[str(message.from_user.id)]
chat = vk.API(session).messages.getChat(
chat_id=currentchat[str(message.from_user.id)].split('group')[1],
fields=[])
if chat['title'].replace('\\', ''): if chat['title'].replace('\\', ''):
chat['title'] = chat['title'].replace('\\', '') chat['title'] = chat['title'].replace('\\', '')
bot.send_message(message.from_user.id, bot.send_message(message.from_user.id,
'<i>Вы в беседе {}</i>'.format(chat['title']), '<i>Вы в беседе {}</i>'.format(chat['title']),
parse_mode='HTML').wait() parse_mode='HTML').wait()
else: else:
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session chat = currentchat[str(message.from_user.id)]
user = vk.API(session).users.get(user_ids=currentchat[str(message.from_user.id)], fields=[])[0]
bot.send_message(message.from_user.id, bot.send_message(message.from_user.id,
'<i>Вы в чате с {} {}</i>'.format(user['first_name'], user['last_name']), '<i>Вы в чате с {}</i>'.format(chat['title']),
parse_mode='HTML').wait() parse_mode='HTML').wait()
else: else:
bot.send_message(message.from_user.id, bot.send_message(message.from_user.id,
@ -352,12 +394,12 @@ def vk_sender(message, method):
elif str(message.from_user.id) in currentchat: elif str(message.from_user.id) in currentchat:
info = [] info = []
if 'group' in currentchat[str(message.from_user.id)]: if 'group' in currentchat[str(message.from_user.id)]['id']:
info.append('0') info.append('0')
info.append(currentchat[str(message.from_user.id)].split('group')[1]) info.append(currentchat[str(message.from_user.id)]['id'].split('group')[1])
info.append('1') info.append('1')
else: else:
info.append(currentchat[str(message.from_user.id)]) info.append(currentchat[str(message.from_user.id)]['id'])
info.append('0') info.append('0')
info.append('0') info.append('0')
form_request(message, method, info) form_request(message, method, info)
@ -381,9 +423,9 @@ def send_text(message, userid, group, forward_messages=None):
def send_doc(message, userid, group, forward_messages=None): def send_doc(message, userid, group, forward_messages=None):
filetype = message.content_type filetype = message.content_type
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
if filetype == 'document' and 'video' not in message.document.mime_type:
file = wget.download( file = wget.download(
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path)) FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
if filetype == 'document':
openedfile = open(file, 'rb') openedfile = open(file, 'rb')
files = {'file': openedfile} files = {'file': openedfile}
fileonserver = ujson.loads(requests.post(vk.API(session).docs.getUploadServer()['upload_url'], fileonserver = ujson.loads(requests.post(vk.API(session).docs.getUploadServer()['upload_url'],
@ -395,6 +437,8 @@ def send_doc(message, userid, group, forward_messages=None):
os.remove(file) os.remove(file)
elif filetype == 'voice': elif filetype == 'voice':
file = wget.download(
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
openedfile = open(file, 'rb') openedfile = open(file, 'rb')
files = {'file': openedfile} files = {'file': openedfile}
fileonserver = ujson.loads( fileonserver = ujson.loads(
@ -405,7 +449,13 @@ def send_doc(message, userid, group, forward_messages=None):
openedfile.close() openedfile.close()
os.remove(file) os.remove(file)
elif filetype == 'document' and 'video' in message.document.mime_type:
vk_sender(message, send_video)
return
else: # filetype == 'audio': else: # filetype == 'audio':
file = wget.download(
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
newfile = file.split('.')[0] + '.aac' newfile = file.split('.')[0] + '.aac'
os.rename(file, newfile) os.rename(file, newfile)
openedfile = open(newfile, 'rb') openedfile = open(newfile, 'rb')
@ -605,9 +655,11 @@ def reply_text(message):
code = extract_unique_code(m.group(0)) code = extract_unique_code(m.group(0))
if check_thread(message.from_user.id): if check_thread(message.from_user.id):
try: try:
verifycode(code) user = verifycode(code)
create_thread(message.from_user.id, code) create_thread(message.from_user.id, code)
bot.send_message(message.from_user.id, 'Вход выполнен!').wait() bot.send_message(message.from_user.id,
'Вход выполнен в аккаунт {} {}!'.format(user['first_name'], user['last_name'])).wait()
bot.send_message(message.from_user.id, '[Использование](https://asergey.me/tgvkbot/usage/)', bot.send_message(message.from_user.id, '[Использование](https://asergey.me/tgvkbot/usage/)',
parse_mode='Markdown').wait() parse_mode='Markdown').wait()
except: except:
@ -624,7 +676,24 @@ def reply_text(message):
except Exception: except Exception:
bot.reply_to(message, 'Произошла неизвестная ошибка при отправке', bot.reply_to(message, 'Произошла неизвестная ошибка при отправке',
parse_mode='Markdown').wait() parse_mode='Markdown').wait()
print('Error: {}'.format(traceback.format_exc()))
bot.polling(none_stop=True) bot.polling(none_stop=True)
"""class WebhookServer(object):
# index равнозначно /, т.к. отсутствию части после ip-адреса (грубо говоря)
@cherrypy.expose
def index(self):
length = int(cherrypy.request.headers['content-length'])
json_string = cherrypy.request.body.read(length).decode("utf-8")
update = telebot.types.Update.de_json(json_string)
bot.process_new_updates([update])
return ''
if __name__ == '__main__':
logging.basicConfig(format='%(levelname)-8s [%(asctime)s] %(message)s', level=logging.WARNING, filename='vk.log')
bot.remove_webhook()
bot.set_webhook('https://{}/{}/'.format(bot_url, token))
cherrypy.config.update(
{'server.socket_host': '127.0.0.1', 'server.socket_port': local_port, 'engine.autoreload.on': False,
'log.screen': False})
cherrypy.quickstart(WebhookServer(), '/', {'/': {}})"""

View File

@ -1,13 +1,18 @@
import logging
import os import os
import redis import redis
import requests import requests
import time import time
import vk import vk
import ujson
import wget import wget
logging.basicConfig(format='%(levelname)-8s [%(asctime)s] %(message)s', level=logging.WARNING, filename='vk.log')
vk_tokens = redis.from_url(os.environ.get("REDIS_URL")) vk_tokens = redis.from_url(os.environ.get("REDIS_URL"))
class VkPolling: class VkPolling:
def __init__(self): def __init__(self):
self._running = True self._running = True
@ -17,14 +22,15 @@ class VkPolling:
def run(self, vk_user, bot, chat_id): def run(self, vk_user, bot, chat_id):
while self._running: while self._running:
updates = [] timeout = 30
try: try:
updates = vk_user.get_new_messages() updates = vk_user.get_new_messages()
except requests.exceptions.ReadTimeout as e:
print('Error: {}'.format(e))
if updates: if updates:
handle_updates(vk_user, bot, chat_id, updates) handle_updates(vk_user, bot, chat_id, updates)
for i in range(60): except requests.exceptions.ReadTimeout:
logging.warning('Retrying VK Polling.')
timeout = 0
for i in range(timeout):
if self._running: if self._running:
time.sleep(0.1) time.sleep(0.1)
else: else:
@ -32,7 +38,12 @@ class VkPolling:
def handle_messages(m, vk_user, bot, chat_id, mainmessage=None): def handle_messages(m, vk_user, bot, chat_id, mainmessage=None):
if m['uid'] > 0:
user = vk.API(vk_user.session).users.get(user_ids=m["uid"], fields=[])[0] user = vk.API(vk_user.session).users.get(user_ids=m["uid"], fields=[])[0]
else:
group = vk.API(vk_user.session).groups.getById(group_ids=str(m['uid'])[1:])[0]
user = {'first_name': group['name'], 'last_name': None}
if 'body' in m and not 'attachment' in m and not 'geo' in m and not 'fwd_messages' in m: if 'body' in m and not 'attachment' in m and not 'geo' in m and not 'fwd_messages' in m:
data = add_user_info(m, user["first_name"], user["last_name"])[:-1] + add_reply_info(m) data = add_user_info(m, user["first_name"], user["last_name"])[:-1] + add_reply_info(m)
bot.send_message(chat_id, data, parse_mode='HTML', disable_web_page_preview=False, bot.send_message(chat_id, data, parse_mode='HTML', disable_web_page_preview=False,
@ -240,15 +251,29 @@ def add_reply_info(m):
def add_user_info(m, first_name, last_name): def add_user_info(m, first_name, last_name):
if 'body' in m and m['body']: if 'body' in m and m['body']:
if last_name:
if 'chat_id' in m: if 'chat_id' in m:
return '<b>{} {} @ {}:</b>\n{}\n'.format(first_name, last_name, m['title'], m['body'].replace('<br>', '\n')) return '<b>{} {} @ {}:</b>\n{}\n'.format(first_name, last_name, m['title'],
m['body'].replace('<br>', '\n'))
else: else:
return '<b>{} {}:</b>\n{}\n'.format(first_name, last_name, m['body'].replace('<br>', '\n')) return '<b>{} {}:</b>\n{}\n'.format(first_name, last_name, m['body'].replace('<br>', '\n'))
else: else:
if 'chat_id' in m:
return '<b>{} @ {}:</b>\n{}\n'.format(first_name, m['title'],
m['body'].replace('<br>', '\n'))
else:
return '<b>{}:</b>\n{}\n'.format(first_name, m['body'].replace('<br>', '\n'))
else:
if last_name:
if 'chat_id' in m: if 'chat_id' in m:
return '<b>{} {} @ {}:</b>\n'.format(first_name, last_name, m['title']) return '<b>{} {} @ {}:</b>\n'.format(first_name, last_name, m['title'])
else: else:
return '<b>{} {}:</b>\n'.format(first_name, last_name) return '<b>{} {}:</b>\n'.format(first_name, last_name)
else:
if 'chat_id' in m:
return '<b>{} @ {}:</b>\n'.format(first_name, m['title'])
else:
return '<b>{}:</b>\n'.format(first_name)
def check_notification(value): def check_notification(value):
@ -277,7 +302,17 @@ class VkMessage:
def get_new_messages(self): def get_new_messages(self):
api = vk.API(self.session) api = vk.API(self.session)
new = api.messages.getLongPollHistory(ts=self.ts, pts=self.pts) try:
ts_pts = ujson.dumps({"ts": self.ts, "pts": self.pts})
new = api.execute(code='return API.messages.getLongPollHistory({});'.format(ts_pts))
except vk.api.VkAPIError:
timeout = 3
logging.warning('Retrying getLongPollHistory in {} seconds'.format(timeout))
time.sleep(timeout)
self.ts, self.pts = get_tses(self.session)
ts_pts = ujson.dumps({"ts": self.ts, "pts": self.pts})
new = api.execute(code='return API.messages.getLongPollHistory({});'.format(ts_pts))
msgs = new['messages'] msgs = new['messages']
self.pts = new["new_pts"] self.pts = new["new_pts"]
count = msgs[0] count = msgs[0]