From 324e2f0ae3f250ee24dc20028f0062f0fa257e93 Mon Sep 17 00:00:00 2001 From: Kylmakalle Date: Tue, 19 Feb 2019 22:57:46 +0300 Subject: [PATCH] =?UTF-8?q?Support=20for=20audio=20=F0=9F=8E=B5.=20Messagi?= =?UTF-8?q?ng=20api=20with=20Kate=20Mobile=20app=20id.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And miserable stability improvements. --- README.md | 6 +++++- bot.py | 16 ++++++++++++++++ config.py | 12 ++++++++++-- docker-compose.yml | 2 +- set_env.py | 15 ++++++++------- telegram.py | 33 +++++++++++++++++++++++++------ vk_messages.py | 48 ++++++++++++++++++++++++++++++++++++++++------ 7 files changed, 109 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 56b1760..2798dba 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,9 @@ chmod +x install.sh ... Telegram Token: 123456789:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLL -VK APP ID: 1234567 +VK APP ID: 1234567 # можно пропустить, будет использован идентификатор Kate Mobile ``` + +# Сервисы музыки +API - https://github.com/Kylmakalle/thatmusic-api +Token Refresher - https://github.com/Kylmakalle/vk-audio-token/tree/refresh-api diff --git a/bot.py b/bot.py index 6f74d11..d7a3fce 100644 --- a/bot.py +++ b/bot.py @@ -102,3 +102,19 @@ async def get_content(url, docname='tgvkbot.document', chrome_headers=True, rewr bot = Bot(token=BOT_TOKEN) dp = Dispatcher(bot) dp.loop.set_task_factory(context.task_factory) +import traceback + + +@dp.errors_handler() +async def all_errors_handler(dp, update, e): + if 'message' in dir(update) and update.message: + user = update.message.from_user.full_name + user_id = update.message.from_user.id + else: + user = update.callback_query.from_user.full_name + user_id = update.callback_query.from_user.id + + logging.error(f'The update was: {update}') + logging.exception(traceback.print_exc()) + + return True diff --git a/config.py b/config.py index 8e14bdf..b78617c 100644 --- a/config.py +++ b/config.py @@ -28,7 +28,15 @@ DATABASE_HOST = os.environ.get('DATABASE_HOST', 'db') DATABASE_PORT = os.environ.get('DATABASE_PORT', '5432') DATABASE_NAME = os.environ.get('POSTGRES_DB', 'tgvkbot') -VK_APP_ID = os.environ.get('VK_APP_ID') +VK_APP_ID = os.environ.get('VK_APP_ID', '2685278') # Kate mobile + +AUDIO_URL = os.environ.get('AUDIO_URL', 'http://thatmusic.akentev.com/id/{owner_id}/{audio_id}') +AUDIO_ACCESS_URL = os.environ.get('AUDIO_ACCESS_URL', + 'http://thatmusic.akentev.com/access_id/{token}/{owner_id}/{audio_id}') +TOKEN_REFRESH_URL = os.environ.get('TOKEN_REFRESH_URL', 'http://thatmusic.akentev.com/refresh') + +AUDIO_HEADERS = { + 'user-agent': 'KateMobileAndroid/52.1 lite-445 (Android 4.4.2; SDK 19; x86; unknown Android SDK built for x86; en)'} BOT_TOKEN = os.environ.get('BOT_TOKEN') @@ -36,7 +44,7 @@ SETTINGS_VAR = os.environ.get('SETTINGS_VAR', 'DJANGO_TGVKBOT_SETTINGS_MODULE') MAX_FILE_SIZE = os.environ.get('MAX_FILE_SIZE', 52428800) -API_VERSION = os.environ.get('API_VERSION', '5.73') +API_VERSION = os.environ.get('API_VERSION', '5.78') # https://www.miniwebtool.com/django-secret-key-generator/ # Возможно достаточно заглушки в стиле 'tgvkbot-super-secret-key(nope)' diff --git a/docker-compose.yml b/docker-compose.yml index cb5df37..c1d41e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,4 +42,4 @@ networks: web_nw: driver: bridge volumes: - dbdata: \ No newline at end of file + dbdata: diff --git a/set_env.py b/set_env.py index 2f00213..9838197 100644 --- a/set_env.py +++ b/set_env.py @@ -1,4 +1,4 @@ -from config import API_VERSION +from config import API_VERSION, VK_APP_ID from urllib.request import urlopen, Request from urllib.error import HTTPError from urllib.parse import urlencode @@ -48,14 +48,15 @@ def set_env(): while True: vk_app_id = input('VK APP ID: ') vk_app_id = vk_app_id.strip() - try: - get_auth_page(vk_app_id) - break - except HTTPError: - print('VK APP ID is invalid, try again!') + if vk_app_id: + try: + get_auth_page(vk_app_id) + break + except HTTPError: + print('VK APP ID is invalid, try again!') with open('env_file', 'w') as env_file: - env_file.write(ENV_FILE_TEMPLATE % {'tg_token': tg_token, 'vk_app_id': vk_app_id}) + env_file.write(ENV_FILE_TEMPLATE % {'tg_token': tg_token, 'vk_app_id': vk_app_id or VK_APP_ID}) print('Success!') diff --git a/telegram.py b/telegram.py index 76f47cd..b965464 100644 --- a/telegram.py +++ b/telegram.py @@ -9,7 +9,8 @@ from vk_messages import vk_polling_tasks, vk_polling log = logging.getLogger('telegram') -oauth_link = re.compile('https://oauth\.vk\.com/blank\.html#access_token=([a-z0-9]*)&expires_in=[0-9]*&user_id=[0-9]*') +oauth_link = re.compile( + 'https://(oauth|api)\.vk\.com/blank\.html#access_token=([a-z0-9]*)&expires_in=[0-9]*&user_id=[0-9]*') async def get_pages_switcher(markup, page, pages): @@ -273,6 +274,22 @@ async def search_dialogs(msg: types.Message, user=None): await bot.send_message(msg.chat.id, text, reply_markup=markup, parse_mode=ParseMode.HTML) +async def refresh_token(vkuser): + try: + with aiohttp.ClientSession() as session: + r = await session.request('GET', TOKEN_REFRESH_URL, params={'token': vkuser.token}) + data = await r.json() + if data['ok']: + vkuser.token = data['token'] + vkuser.save() + session.close() + else: + return False + return True + except: + pass + + @dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('logged')) async def check_logged(call: types.CallbackQuery): vkuser = VkUser.objects.filter(owner__uid=call.from_user.id).count() @@ -512,7 +529,7 @@ async def send_welcome(msg: types.Message): existing_vkuser = VkUser.objects.filter(owner=user).count() if not existing_vkuser: 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,stories' \ + 'display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,messages,offline,docs,photos,video,stories,audio' \ '&response_type=token&v={}'.format(VK_APP_ID, API_VERSION) mark = InlineKeyboardMarkup() login = InlineKeyboardButton('ВХОД', url=link) @@ -651,7 +668,8 @@ async def handle_text(msg: types.Message): if msg.chat.type == 'private': m = oauth_link.search(msg.text) if m: - token = m.group(1) + await bot.send_chat_action(msg.from_user.id, ChatActions.TYPING) + token = m.group(2) if not VkUser.objects.filter(token=token).exists(): try: session = VkSession(access_token=token, driver=await get_driver(token)) @@ -666,10 +684,13 @@ async def handle_text(msg: types.Message): if driver: driver.close() del DRIVERS[vkuser.token] + refreshed_token = await refresh_token(vkuser) TASKS.append({'token': vkuser.token, 'task': asyncio.ensure_future(vk_polling(vkuser))}) - await msg.reply( - 'Вход выполнен в аккаунт {} {}!\n[Использование](https://asergey.me/tgvkbot/usage/)'.format( + logged_in = await msg.reply( + 'Вход выполнен в аккаунт {} {}!\n[Использование](https://akentev.com/tgvkbot/usage/)'.format( vkuserinfo['first_name'], vkuserinfo.get('last_name', '')), parse_mode='Markdown') + if refreshed_token: + await logged_in.reply('*Вам доступна музыка 🎵*', parse_mode='Markdown') except VkAuthError: await msg.reply('Неверная ссылка, попробуйте ещё раз!') else: @@ -724,7 +745,7 @@ async def handle_photo(msg: types.Message): if not vk_message: await msg.reply('Произошла ошибка при отправке', parse_mode=ParseMode.HTML) else: - msg.reply('Ошибка при загрузке файла. Сообщение не отправлено!', parse_mode=ParseMode.HTML) + await msg.reply('Ошибка при загрузке файла. Сообщение не отправлено!', parse_mode=ParseMode.HTML) @dp.message_handler(content_types=['document', 'voice', 'audio', 'sticker']) diff --git a/vk_messages.py b/vk_messages.py index 3b9a9a5..3d41939 100644 --- a/vk_messages.py +++ b/vk_messages.py @@ -555,7 +555,7 @@ async def process_message(msg, token=None, is_multichat=None, vk_chat_id=None, u disable_notify = bool(vk_msg.get('push_settings', False)) attaches_scheme = [] if vk_msg.get('attachments'): - attaches_scheme = [await process_attachment(attachment) for attachment in + attaches_scheme = [await process_attachment(attachment, token) for attachment in vk_msg['attachments']] if vk_msg.get('geo'): location = vk_msg['geo']['coordinates'].split(' ') @@ -608,7 +608,10 @@ async def process_message(msg, token=None, is_multichat=None, vk_chat_id=None, u if check_url: body_parts[body_part] = body_parts[body_part].replace(i.group(0), hlink(f'{i.group(2)}', url=vk_url)) - await bot.send_chat_action(to_tg_chat, ChatActions.TYPING) + try: + await bot.send_chat_action(to_tg_chat, ChatActions.TYPING) + except: + return tg_message = await bot.send_message(vkuser.owner.uid, body_parts[body_part], parse_mode=ParseMode.HTML, reply_to_message_id=main_message, @@ -630,7 +633,10 @@ async def process_message(msg, token=None, is_multichat=None, vk_chat_id=None, u check_url = await check_vk_url(vk_url) if check_url: body = body.replace(i.group(0), hlink(f'{i.group(2)}', url=vk_url)) - await bot.send_chat_action(to_tg_chat, ChatActions.TYPING) + try: + await bot.send_chat_action(to_tg_chat, ChatActions.TYPING) + except: + return header_message = tg_message = await bot.send_message(to_tg_chat, header + body, parse_mode=ParseMode.HTML, reply_to_message_id=main_message, @@ -697,7 +703,10 @@ async def process_message(msg, token=None, is_multichat=None, vk_chat_id=None, u await bot.send_chat_action(to_tg_chat, ChatActions.FIND_LOCATION) tg_message = await tgsend(bot.send_venue, to_tg_chat, *attachment['content'], reply_to_message_id=main_message, disable_notification=disable_notify) - + elif attachment['type'] == 'audio': + await bot.send_chat_action(to_tg_chat, ChatActions.UPLOAD_DOCUMENT) + tg_message = await tgsend(bot.send_audio, to_tg_chat, audio=attachment['content'], + reply_to_message_id=main_message, disable_notification=disable_notify) Message.objects.create( vk_chat=vk_chat_id, vk_id=vk_msg_id, @@ -753,14 +762,41 @@ async def check_vk_url(url): return False -async def process_attachment(attachment): +async def process_attachment(attachment, token=None): atype = attachment.get('type') if atype == 'photo': photo_url = attachment[atype][await get_max_photo(attachment[atype])] return {'content': photo_url, 'type': 'photo'} elif atype == 'audio': - pass + if AUDIO_ACCESS_URL: + if token: + try: + with aiohttp.ClientSession() as session: + r = await session.request('GET', AUDIO_ACCESS_URL.format(token=token, + owner_id=attachment[atype]['owner_id'], + audio_id=attachment[atype]['id'])) + if r.status != 200: + raise Exception + audio = await r.read() + audio = io.BytesIO(audio) + return {'content': audio, 'type': 'audio'} + except: + pass + elif AUDIO_URL: + try: + with aiohttp.ClientSession() as session: + r = await session.request('GET', AUDIO_URL.format(owner_id=attachment[atype]['owner_id'], + audio_id=attachment[atype]['id'])) + if r.status != 200: + raise Exception + audio = await r.read() + audio = io.BytesIO(audio) + return {'content': audio, 'type': 'audio'} + except: + pass + + return {'content': 'Аудио', 'type': 'text'} elif atype == 'video': title = attachment[atype]['title']