Init
This commit is contained in:
commit
d7c1ebc498
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.idea/
|
||||
__pycache__/
|
||||
data/migrations/*
|
||||
!data/migrations/__init__.py
|
||||
config.py
|
102
bot.py
Normal file
102
bot.py
Normal file
@ -0,0 +1,102 @@
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
import tempfile
|
||||
import ujson
|
||||
from pprint import pprint
|
||||
|
||||
import aiohttp
|
||||
import django.conf
|
||||
import wget
|
||||
from PIL import Image
|
||||
from aiogram import Bot
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.types import ParseMode, MediaGroup, InlineKeyboardMarkup, InlineKeyboardButton, ChatActions
|
||||
from aiogram.utils.exceptions import *
|
||||
from aiogram.utils.parts import safe_split_text, split_text, MAX_MESSAGE_LENGTH
|
||||
from aiovk import TokenSession, API
|
||||
from aiovk.drivers import HttpDriver
|
||||
from aiovk.exceptions import *
|
||||
from aiovk.mixins import LimitRateDriverMixin
|
||||
|
||||
from config import *
|
||||
|
||||
django.conf.ENVIRONMENT_VARIABLE = SETTINGS_VAR
|
||||
os.environ.setdefault(SETTINGS_VAR, "settings")
|
||||
# Ensure settings are read
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
from data.models import *
|
||||
|
||||
|
||||
class VkSession(TokenSession):
|
||||
API_VERSION = API_VERSION
|
||||
|
||||
|
||||
class RateLimitedDriver(LimitRateDriverMixin, HttpDriver):
|
||||
requests_per_period = 1
|
||||
period = 0.4
|
||||
|
||||
|
||||
DRIVERS = {}
|
||||
|
||||
|
||||
async def get_driver(vk_token=None):
|
||||
if vk_token:
|
||||
if vk_token in DRIVERS:
|
||||
return DRIVERS[vk_token]
|
||||
else:
|
||||
new_driver = RateLimitedDriver()
|
||||
DRIVERS[vk_token] = new_driver
|
||||
return new_driver
|
||||
else:
|
||||
return RateLimitedDriver()
|
||||
|
||||
|
||||
async def get_vk_chat(cid):
|
||||
return VkChat.objects.get_or_create(cid=cid)
|
||||
|
||||
|
||||
max_photo_re = re.compile('photo_([0-9]*)')
|
||||
|
||||
|
||||
async def get_max_photo(obj, keyword='photo'):
|
||||
maxarr = []
|
||||
for k, v in obj.items():
|
||||
m = max_photo_re.match(k)
|
||||
if m:
|
||||
maxarr.append(int(m.group(1)))
|
||||
return keyword + '_' + str(max(maxarr))
|
||||
|
||||
|
||||
async def get_content(url, docname='tgvkbot.document', chrome_headers=True, rewrite_name=False,
|
||||
custom_ext=''):
|
||||
try:
|
||||
with aiohttp.ClientSession(headers={
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'} if chrome_headers else {}) as session:
|
||||
r = await session.request('GET', url)
|
||||
direct_url = str(r.url)
|
||||
tempdir = tempfile.gettempdir()
|
||||
filename_options = {'out': docname} if rewrite_name else {'default': docname}
|
||||
if direct_url != url:
|
||||
r.release()
|
||||
c = await session.request('GET', direct_url)
|
||||
file = wget.detect_filename(direct_url, headers=dict(c.headers), **filename_options)
|
||||
temppath = os.path.join(tempdir, file + custom_ext)
|
||||
with open(temppath, 'wb') as f:
|
||||
f.write(await c.read())
|
||||
else:
|
||||
file = wget.detect_filename(direct_url, headers=dict(r.headers), **filename_options)
|
||||
temppath = os.path.join(tempdir, file + custom_ext)
|
||||
with open(temppath, 'wb') as f:
|
||||
f.write(await r.read())
|
||||
content = open(temppath, 'rb')
|
||||
return {'content': content, 'file_name': file, 'custom_ext': custom_ext, 'temp_path': tempdir}
|
||||
except Exception:
|
||||
return {'url': url, 'docname': docname}
|
||||
|
||||
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
dp = Dispatcher(bot)
|
0
data/__init__.py
Normal file
0
data/__init__.py
Normal file
0
data/migrations/__init__.py
Normal file
0
data/migrations/__init__.py
Normal file
105
data/models.py
Normal file
105
data/models.py
Normal file
@ -0,0 +1,105 @@
|
||||
import asyncio
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
class AsyncManager(models.Manager):
|
||||
""" A model manager which uses the AsyncQuerySet. """
|
||||
|
||||
async def get_query_set(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, AsyncQuerySet(self.model, using=self._db))
|
||||
|
||||
|
||||
class AsyncQuerySet(QuerySet):
|
||||
""" A queryset which allows DB operations to be pre-triggered so that they run in the
|
||||
background while the application can continue doing other processing.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AsyncQuerySet, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class TgUser(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
# id пользователя на сервере Telegram
|
||||
uid = models.BigIntegerField(unique=True)
|
||||
|
||||
# имя
|
||||
first_name = models.CharField(max_length=256)
|
||||
|
||||
# фамилия
|
||||
last_name = models.CharField(
|
||||
max_length=256,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
# username
|
||||
username = models.CharField(
|
||||
max_length=256,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
BLOCKED = -1
|
||||
BASE = 0
|
||||
|
||||
STATUSES = (
|
||||
(BLOCKED, 'Заблокирован'),
|
||||
(BASE, 'Базовый'),
|
||||
)
|
||||
|
||||
status = models.IntegerField(
|
||||
choices=STATUSES,
|
||||
default=BASE
|
||||
)
|
||||
|
||||
|
||||
class VkUser(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
token = models.TextField(unique=True)
|
||||
is_polling = models.BooleanField(default=False)
|
||||
owner = models.ForeignKey(TgUser, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class VkChat(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
cid = models.BigIntegerField(unique=True)
|
||||
|
||||
|
||||
class TgChat(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
cid = models.BigIntegerField(unique=True)
|
||||
|
||||
|
||||
class Forward(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
owner = models.ForeignKey(TgUser, on_delete=models.CASCADE)
|
||||
tgchat = models.ForeignKey(TgChat, on_delete=models.CASCADE)
|
||||
vkchat = models.ForeignKey(VkChat, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
vk_chat = models.BigIntegerField()
|
||||
vk_id = models.BigIntegerField(null=True)
|
||||
tg_chat = models.BigIntegerField()
|
||||
tg_id = models.BigIntegerField()
|
||||
|
||||
|
||||
class MessageMarkup(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
message_id = models.BigIntegerField()
|
||||
|
||||
chat_id = models.BigIntegerField()
|
||||
|
||||
buttons = models.TextField(null=True, blank=True)
|
24
manage.py
Normal file
24
manage.py
Normal file
@ -0,0 +1,24 @@
|
||||
import sys
|
||||
from config import *
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault(SETTINGS_VAR, "settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
)
|
||||
raise
|
||||
import django.conf
|
||||
|
||||
django.conf.ENVIRONMENT_VARIABLE = SETTINGS_VAR
|
||||
execute_from_command_line(sys.argv)
|
17
settings.py
Normal file
17
settings.py
Normal file
@ -0,0 +1,17 @@
|
||||
from config import *
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': DATABASE_NAME,
|
||||
'USER': DATABASE_USER,
|
||||
'PASSWORD': DATABASE_PASSWORD,
|
||||
'HOST': DATABASE_HOST,
|
||||
'PORT': DATABASE_PORT
|
||||
}
|
||||
}
|
||||
INSTALLED_APPS = (
|
||||
'data',
|
||||
)
|
881
telegram.py
Normal file
881
telegram.py
Normal file
@ -0,0 +1,881 @@
|
||||
from aiogram import types
|
||||
from aiogram.bot.api import FILE_URL
|
||||
from aiogram.dispatcher.webhook import get_new_configured_app
|
||||
from aiohttp import web
|
||||
|
||||
from bot import *
|
||||
from config import *
|
||||
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]*')
|
||||
|
||||
|
||||
async def get_pages_switcher(markup, page, pages):
|
||||
if page != 0:
|
||||
leftbutton = InlineKeyboardButton('◀', callback_data='page{}'.format(page - 1)) # callback
|
||||
else:
|
||||
leftbutton = InlineKeyboardButton('Поиск 🔍', callback_data='search')
|
||||
if page + 1 < len(pages):
|
||||
rightbutton = InlineKeyboardButton('▶', callback_data='page{}'.format(page + 1))
|
||||
else:
|
||||
rightbutton = None
|
||||
|
||||
if rightbutton:
|
||||
markup.row(leftbutton, rightbutton)
|
||||
else:
|
||||
markup.row(leftbutton)
|
||||
|
||||
|
||||
async def logged(uid, reply_to_message_id=None, to_chat=None):
|
||||
vk_user = VkUser.objects.filter(owner__uid=uid).first()
|
||||
if vk_user:
|
||||
return True
|
||||
else:
|
||||
await bot.send_message(to_chat or uid, 'Вход не выполнен! /start для входа',
|
||||
reply_to_message_id=reply_to_message_id)
|
||||
return False
|
||||
|
||||
|
||||
async def update_user_info(from_user: types.User):
|
||||
return TgUser.objects.update_or_create(uid=from_user.id,
|
||||
defaults={'first_name': from_user.first_name,
|
||||
'last_name': from_user.last_name,
|
||||
'username': from_user.username
|
||||
})
|
||||
|
||||
|
||||
async def update_chat_info(from_chat: types.Chat):
|
||||
if from_chat.type == 'private':
|
||||
return None, False
|
||||
return TgChat.objects.update_or_create(cid=from_chat.id)
|
||||
|
||||
|
||||
async def is_forwarding(text):
|
||||
if not text:
|
||||
return False, None
|
||||
if text == '!':
|
||||
return True, None
|
||||
if text.startswith('!'):
|
||||
return True, text[1:]
|
||||
return False, text
|
||||
|
||||
|
||||
async def is_bot_in_iterator(msg: types.Message):
|
||||
iterator = msg.new_chat_members or [msg.left_chat_member] or []
|
||||
me = await bot.me
|
||||
for i in iterator:
|
||||
if me.id == i.id:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def vk_sender(token, tg_message, **kwargs):
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
try:
|
||||
api = API(session)
|
||||
vk_msg_id = await api('messages.send', **kwargs)
|
||||
except VkAuthError:
|
||||
vk_user = VkUser.objects.filter(token=token).first()
|
||||
if vk_user:
|
||||
vk_user.delete()
|
||||
await bot.send_message(tg_message.chat.id, 'Вход не выполнен! /start для входа',
|
||||
reply_to_message_id=tg_message.message_id)
|
||||
return
|
||||
except VkAPIError:
|
||||
log.exception(msg='Error in vk sender', exc_info=True)
|
||||
return None
|
||||
except Exception:
|
||||
log.exception(msg='Error in vk sender', exc_info=True)
|
||||
return None
|
||||
Message.objects.create(
|
||||
vk_chat=kwargs['peer_id'],
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
return vk_msg_id
|
||||
|
||||
|
||||
async def generate_send_options(msg, forward=None, forward_messages_exists=False, message=None):
|
||||
message_options = dict()
|
||||
if forward:
|
||||
if msg.reply_to_message is not None:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id,
|
||||
tg_id=msg.reply_to_message.message_id).first()
|
||||
if message_in_db and message_in_db.vk_id:
|
||||
message_options['forward_messages'] = message_in_db.vk_id
|
||||
message_options['peer_id'] = forward.vkchat.cid
|
||||
|
||||
elif msg.reply_to_message is not None:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id, tg_id=msg.reply_to_message.message_id).first()
|
||||
if not message_in_db:
|
||||
await msg.reply('Не знаю в какой чат ответить, нет информации в базе данных.')
|
||||
return message_options
|
||||
if forward_messages_exists and message_in_db.vk_id:
|
||||
message_options['forward_messages'] = message_in_db.vk_id
|
||||
message_options['peer_id'] = message_in_db.vk_chat
|
||||
else:
|
||||
await msg.reply('Не понимаю что делать. Нужна помощь? Используй команду /help')
|
||||
return message_options
|
||||
|
||||
if message:
|
||||
message_options['message'] = message
|
||||
return message_options
|
||||
|
||||
|
||||
async def send_vk_action(token, peer_id, action='typing'):
|
||||
vksession = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(vksession)
|
||||
return await api('messages.setActivity', peer_id=peer_id, activity=action)
|
||||
|
||||
|
||||
async def upload_attachment(msg, vk_user, file_id, peer_id, attachment_type, upload_field, upload_method,
|
||||
on_server_field='file', save_method='', upload_type=None, default_name='tgvkbot.document',
|
||||
title='tgvkbot.document', rewrite_name=False, custom_ext=''):
|
||||
try:
|
||||
file_info = await bot.get_file(file_id)
|
||||
path = file_info['file_path']
|
||||
if msg.content_type == 'audio':
|
||||
if not custom_ext and '.' in path and path.split('.')[-1] == 'mp3':
|
||||
custom_ext = '.aac'
|
||||
except NetworkError:
|
||||
await msg.reply('Файл слишком большой, максимально допустимый размер <b>20мб!</b>', parse_mode=ParseMode.HTML)
|
||||
return
|
||||
|
||||
url = FILE_URL.format(token=bot._BaseBot__token, path=path)
|
||||
await send_vk_action(vk_user.token, peer_id)
|
||||
content = await get_content(url, default_name, chrome_headers=False, rewrite_name=rewrite_name,
|
||||
custom_ext=custom_ext)
|
||||
filename = (content.get('file_name', '') + content.get('custom_ext', '')) or None
|
||||
if 'content' in content:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
upload_options = {}
|
||||
if attachment_type != 'photo' and upload_type:
|
||||
upload_options['type'] = upload_type
|
||||
if msg.content_type == 'sticker':
|
||||
webp = Image.open(content['content']).convert('RGBA')
|
||||
png = io.BytesIO()
|
||||
webp.save(png, format='png')
|
||||
content['content'] = png.getvalue()
|
||||
if attachment_type == 'video':
|
||||
upload_options['is_private'] = 1
|
||||
upload_server = await api(upload_method, **upload_options)
|
||||
with aiohttp.ClientSession() as session:
|
||||
data = aiohttp.FormData()
|
||||
field_data = {}
|
||||
if filename:
|
||||
field_data['filename'] = filename
|
||||
data.add_field(upload_field, content['content'], content_type='multipart/form-data', **field_data)
|
||||
async with session.post(upload_server['upload_url'], data=data) as upload:
|
||||
file_on_server = ujson.loads(await upload.text())
|
||||
if msg.content_type != 'sticker':
|
||||
content['content'].close()
|
||||
os.remove(os.path.join(content['temp_path'], content['file_name'] + content['custom_ext']))
|
||||
if attachment_type == 'photo':
|
||||
save_options = {'server': file_on_server['server'], on_server_field: file_on_server[on_server_field],
|
||||
'hash': file_on_server['hash']}
|
||||
elif attachment_type == 'video':
|
||||
return f'{attachment_type}{upload_server["owner_id"]}_{upload_server["video_id"]}_{upload_server["access_key"]}'
|
||||
else:
|
||||
if 'file' not in file_on_server:
|
||||
await msg.reply('<b>Ошибка</b> Не удалось загрузить файл. Файл не должен быть исполняемым.',
|
||||
parse_mode=ParseMode.HTML)
|
||||
return
|
||||
save_options = dict({'file': file_on_server['file']})
|
||||
save_options['title'] = title
|
||||
attachment = await api(save_method, **save_options)
|
||||
return f'{attachment_type}{attachment[0]["owner_id"]}_{attachment[0]["id"]}'
|
||||
|
||||
|
||||
async def get_dialogs(token, exclude=list()):
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(session)
|
||||
dialogs = await api('messages.getDialogs', count=200)
|
||||
order = []
|
||||
users_ids = []
|
||||
group_ids = []
|
||||
for chat in dialogs.get('items'):
|
||||
chat = chat.get('message', '')
|
||||
if chat:
|
||||
if 'chat_id' in chat:
|
||||
if 2000000000 + chat['chat_id'] not in exclude:
|
||||
chat['title'] = chat['title']
|
||||
order.append({'title': chat['title'], 'id': 2000000000 + chat['chat_id']})
|
||||
elif chat['user_id'] > 0:
|
||||
if chat['user_id'] not in exclude:
|
||||
order.append({'title': 'Диалог ' + str(chat['user_id']), 'id': chat['user_id']})
|
||||
users_ids.append(chat['user_id'])
|
||||
elif chat['user_id'] < 0:
|
||||
if chat['user_id'] not in exclude:
|
||||
order.append({'title': 'Диалог ' + str(chat['user_id']), 'id': chat['user_id']})
|
||||
group_ids.append(chat['user_id'])
|
||||
|
||||
if users_ids:
|
||||
users = await api('users.get', user_ids=', '.join(str(x) for x in users_ids))
|
||||
else:
|
||||
users = []
|
||||
|
||||
if group_ids:
|
||||
groups = await api('groups.getById', group_ids=', '.join(str(abs(x)) for x in group_ids))
|
||||
else:
|
||||
groups = []
|
||||
|
||||
for output in order:
|
||||
if output['id'] > 0:
|
||||
u = next((i for i in users if i['id'] == output['id']), None)
|
||||
if u:
|
||||
output['title'] = f'{u["first_name"]} {u["last_name"]}'
|
||||
else:
|
||||
g = next((i for i in groups if -i['id'] == output['id']), None)
|
||||
if g:
|
||||
output['title'] = g["name"]
|
||||
|
||||
for button in range(len(order)):
|
||||
order[button] = InlineKeyboardButton(order[button]['title'], callback_data=f'chat{order[button]["id"]}')
|
||||
|
||||
rows = [order[x:x + 2] for x in range(0, len(order), 2)]
|
||||
pages = [rows[x:x + 4] for x in range(0, len(rows), 4)]
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
async def search_dialogs(msg: types.Message, user=None):
|
||||
if not user:
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
markup = InlineKeyboardMarkup(row_width=1)
|
||||
await bot.send_chat_action(msg.chat.id, 'typing')
|
||||
result = await api('messages.searchDialogs', q=msg.text, limit=10)
|
||||
for chat in result:
|
||||
title = None
|
||||
data = None
|
||||
if chat['type'] == 'profile':
|
||||
title = f'{chat["first_name"]} {chat["last_name"]}'
|
||||
data = f'chat{chat["id"]}'
|
||||
elif chat['type'] == 'chat':
|
||||
title = chat['title']
|
||||
data = f'chat{2000000000 + chat["id"]}'
|
||||
elif chat['type'] == 'page':
|
||||
title = await chat['name']
|
||||
data = f'chat{-chat["id"]}'
|
||||
if title and data:
|
||||
markup.add(InlineKeyboardButton(text=title, callback_data=data))
|
||||
markup.add(InlineKeyboardButton('Поиск 🔍', callback_data='search'))
|
||||
if markup.inline_keyboard:
|
||||
text = f'<b>Результат поиска по</b> <i>{msg.text}</i>'
|
||||
else:
|
||||
text = f'<b>Результат поиска по</b> <i>{msg.text}</i>'
|
||||
await bot.send_message(msg.chat.id, text, reply_markup=markup, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@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()
|
||||
if vkuser:
|
||||
await handle_join(call.message, edit=True, chat_id=call.message.chat.id, message_id=call.message.message_id,
|
||||
exclude=True)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('page'))
|
||||
async def page_switcher(call: types.CallbackQuery):
|
||||
# user, created = await update_user_info(call.from_user)
|
||||
# tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
page = int(call.data.split('page')[-1])
|
||||
message_markup = MessageMarkup.objects.filter(
|
||||
chat_id=call.message.chat.id,
|
||||
message_id=call.message.message_id,
|
||||
).first()
|
||||
if message_markup:
|
||||
pages = ujson.loads(message_markup.buttons)
|
||||
markup = InlineKeyboardMarkup()
|
||||
for row in pages[page]:
|
||||
markup.row(*[InlineKeyboardButton(**button) for button in row])
|
||||
await get_pages_switcher(markup, page, pages)
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=markup)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Нет данных в Базе Данных', show_alert=True)
|
||||
|
||||
|
||||
async def get_dialog_info(api, vk_chat_id, name_case='nom'):
|
||||
title = ''
|
||||
photo = ''
|
||||
dialog_type = ''
|
||||
if vk_chat_id >= 2000000000:
|
||||
dialog_info = await api('messages.getChat', chat_id=vk_chat_id - 2000000000)
|
||||
title = await dialog_info['title']
|
||||
photo = dialog_info[await get_max_photo(dialog_info)]
|
||||
dialog_type = 'chat'
|
||||
elif vk_chat_id > 0:
|
||||
dialog_info = await api('users.get', user_ids=vk_chat_id, fields='photo_max', name_case=name_case)
|
||||
first_name = dialog_info[0]['first_name']
|
||||
last_name = dialog_info[0]['last_name'] or ''
|
||||
title = first_name + ' ' + last_name
|
||||
photo = dialog_info[0]['photo_max']
|
||||
dialog_type = 'user'
|
||||
elif vk_chat_id < 0:
|
||||
dialog_info = await api('groups.getById', group_ids=abs(vk_chat_id))
|
||||
title = dialog_info[0]['name']
|
||||
photo = dialog_info[0][await get_max_photo(dialog_info[0])]
|
||||
dialog_type = 'group'
|
||||
|
||||
return {'title': title, 'photo': photo, 'type': dialog_type}
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('ping'))
|
||||
async def ping_button(call: types.CallbackQuery):
|
||||
tg_chat_id = int(call.data.split('ping')[-1])
|
||||
try:
|
||||
await bot.send_message(tg_chat_id, f'<a href="tg://user?id={call.from_user.id}">Ping!</a>',
|
||||
parse_mode=ParseMode.HTML)
|
||||
await bot.answer_callback_query(call.id, 'Ping!')
|
||||
except BadRequest:
|
||||
await bot.answer_callback_query(call.id, 'Нет доступа к чату, бот кикнут или чат удалён!', show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(
|
||||
func=lambda call: call and call.message and call.data and call.data.startswith('deleteforward'))
|
||||
async def delete_forward(call: types.CallbackQuery):
|
||||
forward_id = int(call.data.split('deleteforward')[-1])
|
||||
forward_in_db = Forward.objects.filter(id=forward_id).first()
|
||||
if forward_in_db:
|
||||
forward_in_db.delete()
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
|
||||
message_markup = MessageMarkup.objects.filter(
|
||||
message_id=call.message.message_id,
|
||||
chat_id=call.message.chat.id
|
||||
).first()
|
||||
|
||||
buttons = ujson.loads(message_markup.buttons)
|
||||
|
||||
for row in buttons:
|
||||
if row[1]['callback_data'] == call.data:
|
||||
buttons.remove(row)
|
||||
else:
|
||||
markup.row(*[InlineKeyboardButton(**button) for button in row])
|
||||
if message_markup:
|
||||
if buttons:
|
||||
message_markup.buttons = ujson.dumps(buttons)
|
||||
message_markup.save()
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=markup)
|
||||
else:
|
||||
await bot.edit_message_text(
|
||||
'У Вас нет связанных чатов. Чтобы привязать чат, добавьте бота в группу, а если бот уже добавлен - используйте команду /dialogs',
|
||||
call.message.chat.id, call.message.message_id)
|
||||
await bot.answer_callback_query(call.id, 'Успешно удалено!')
|
||||
else:
|
||||
await bot.edit_message_text('<b>Что-то пошло не так, нет информации в Базе Данных</b>',
|
||||
message_id=call.message.message_id, chat_id=call.message.chat.id, reply_markup=None)
|
||||
await bot.answer_callback_query(call.id, 'Ошибка!')
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('setinfo'))
|
||||
async def set_info(call: types.CallbackQuery):
|
||||
user, created = await update_user_info(call.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
vk_chat_id = int(call.data.split('setinfo')[-1])
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if vkuser:
|
||||
ME = await bot.me
|
||||
can_edit = False
|
||||
if not call.message.chat.all_members_are_administrators and (
|
||||
(await bot.get_chat_member(call.message.chat.id, ME.id)).status == 'administrator'):
|
||||
can_edit = True
|
||||
if not can_edit:
|
||||
admins = await bot.get_chat_administrators(call.message.chat.id)
|
||||
for admin in admins:
|
||||
if admin.user.id == ME.id and admin.can_change_info:
|
||||
can_edit = True
|
||||
break
|
||||
if can_edit:
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='nom')
|
||||
if dialog_info.get('title', ''):
|
||||
await bot.set_chat_title(call.message.chat.id, dialog_info['title'])
|
||||
if dialog_info.get('photo', ''):
|
||||
content = await get_content(dialog_info['photo'])
|
||||
await bot.set_chat_photo(call.message.chat.id, content['content'])
|
||||
content['content'].close()
|
||||
os.remove(os.path.join(content['temp_path'], content['file_name'] + content['custom_ext']))
|
||||
|
||||
if dialog_info['type'] == 'user':
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='ins')
|
||||
text = f'Чат успешно привязан к диалогу c <i>{dialog_info["title"]}</i>'
|
||||
elif dialog_info['type'] == 'group':
|
||||
text = f'Чат успешно привязан к диалогу с сообществом <i>{dialog_info["title"]}</i>'
|
||||
else:
|
||||
text = f'Чат успешно привязан к диалогу <i>{dialog_info["title"]}</i>'
|
||||
|
||||
await bot.edit_message_text(text, call.message.chat.id, call.message.message_id, parse_mode=ParseMode.HTML)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id,
|
||||
'Недостаточно прав для редактирования информации о группе или бот не администратор!',
|
||||
show_alert=True)
|
||||
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('chat'))
|
||||
async def choose_chat(call: types.CallbackQuery):
|
||||
user, created = await update_user_info(call.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
vk_chat_id = int(call.data.split('chat')[-1])
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if vkuser:
|
||||
if call.message.chat.type == 'private':
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='gen')
|
||||
markup = types.ForceReply(selective=False)
|
||||
if dialog_info['type'] == 'user':
|
||||
text = f'Сообщение для <i>{dialog_info["title"]}</i>'
|
||||
elif dialog_info['type'] == 'group':
|
||||
text = f'Сообщение сообществу <i>{dialog_info["title"]}</i>'
|
||||
else:
|
||||
text = f'Сообщение в диалог <i>{dialog_info["title"]}</i>'
|
||||
|
||||
tg_message = await bot.send_message(call.message.chat.id, text, reply_markup=markup,
|
||||
parse_mode=ParseMode.HTML)
|
||||
Message.objects.create(
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id,
|
||||
vk_chat=vk_chat_id
|
||||
)
|
||||
else:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
vkchat = (await get_vk_chat(int(vk_chat_id)))[0]
|
||||
if forward:
|
||||
forward.vkchat = vkchat
|
||||
forward.save()
|
||||
else:
|
||||
Forward.objects.create(
|
||||
tgchat=tgchat,
|
||||
vkchat=vkchat,
|
||||
owner=user
|
||||
)
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('Установить аватар и название', callback_data=f'setinfo{vkchat.cid}'))
|
||||
await bot.edit_message_text(
|
||||
'Чат успешно привязан. Я могу автоматически изменить название и установить аватар, сделай бота администратором и убедись в наличии прав на редактирование информации группы',
|
||||
call.message.chat.id, call.message.message_id, reply_markup=markup)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data == 'search')
|
||||
async def search_callback(call: types.CallbackQuery):
|
||||
vkuser = VkUser.objects.filter(owner__uid=call.from_user.id).count()
|
||||
if vkuser:
|
||||
markup = types.ForceReply(selective=False)
|
||||
await bot.send_message(call.message.chat.id, '<b>Поиск беседы 🔍</b>', parse_mode=ParseMode.HTML,
|
||||
reply_markup=markup)
|
||||
await bot.answer_callback_query(call.id, 'Поиск беседы 🔍')
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if not tgchat:
|
||||
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' \
|
||||
'&response_type=token&v={}'.format(VK_APP_ID, API_VERSION)
|
||||
mark = InlineKeyboardMarkup()
|
||||
login = InlineKeyboardButton('ВХОД', url=link)
|
||||
mark.add(login)
|
||||
await msg.reply('Привет, этот бот поможет тебе общаться ВКонтакте, войди по кнопке ниже'
|
||||
' и отправь мне то, что получишь в адресной строке.',
|
||||
reply_markup=mark)
|
||||
else:
|
||||
await msg.reply('Вход уже выполнен!\n/logout для выхода.')
|
||||
else:
|
||||
markup = InlineKeyboardMarkup()
|
||||
me = await bot.me
|
||||
markup.add(InlineKeyboardButton('Перейти в бота', url=f'https://t.me/{me.username}?start=login'))
|
||||
await msg.reply('Залогиниться можно только через личный чат с ботом', reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['stop'])
|
||||
async def stop_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
|
||||
existing_vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if not existing_vkuser:
|
||||
await msg.reply('Вход не выполнен! Используй команду /start для входа')
|
||||
else:
|
||||
polling = next((task for task in TASKS if task['token'] == existing_vkuser.token), None)
|
||||
if polling:
|
||||
polling['task'].cancel()
|
||||
driver = DRIVERS.get(existing_vkuser.token, '')
|
||||
if driver:
|
||||
driver.close()
|
||||
existing_vkuser.delete()
|
||||
await msg.reply('Успешный выход!')
|
||||
|
||||
|
||||
@dp.message_handler(commands=['dialogs', 'd'])
|
||||
async def dialogs_command(msg: types.Message):
|
||||
if msg.chat.type == 'private':
|
||||
await handle_join(msg, text='Выберите диалог для быстрого ответа')
|
||||
else:
|
||||
await handle_join(msg, exclude=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['read', 'r'])
|
||||
async def read_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if msg.chat.type == 'private':
|
||||
if msg.reply_to_message:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id,
|
||||
tg_id=msg.reply_to_message.message_id).first()
|
||||
if message_in_db:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
await api('messages.markAsRead', peer_id=message_in_db.vk_chat)
|
||||
await bot.send_message(msg.chat.id, '<i>Диалог прочитан</i>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('Не знаю какой чат прочесть, нет информации в базе данных')
|
||||
else:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
if forward:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
await api('messages.markAsRead', peer_id=forward.vkchat.cid)
|
||||
await bot.send_message(msg.chat.id, '<i>Диалог прочитан</i>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('Этот чат не привязан к диалогу ВКонтакте, для привязки используй команду /dialogs')
|
||||
|
||||
|
||||
@dp.message_handler(commands=['search', 's'])
|
||||
async def search_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
vkuser = VkUser.objects.filter(owner=user).count()
|
||||
if vkuser:
|
||||
markup = types.ForceReply(selective=False)
|
||||
await bot.send_message(msg.chat.id, '<b>Поиск беседы 🔍</b>', parse_mode=ParseMode.HTML,
|
||||
reply_markup=markup)
|
||||
else:
|
||||
await bot.answer_callback_query(msg, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['chat', 'chats'])
|
||||
async def chat_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
forwards = Forward.objects.filter(owner=user)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
if forwards:
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
markup = InlineKeyboardMarkup()
|
||||
for forward in forwards:
|
||||
chat = await get_dialog_info(api, forward.vkchat.cid)
|
||||
markup.row(*[InlineKeyboardButton(chat['title'], callback_data=f'ping{forward.tgchat.cid}'),
|
||||
InlineKeyboardButton('❌', callback_data=f'deleteforward{forward.pk}')])
|
||||
msg_with_markup = await bot.send_message(msg.chat.id,
|
||||
'Список привязанных диалогов\nНажав на имя диалога, бот пинганёт Вас в соответствующем чате Telegram.\nНажав на "❌", привязка чата будет удалена и все сообщение из диалога ВКонтакте будут попадать напрямую к боту',
|
||||
reply_markup=markup)
|
||||
for row in markup.inline_keyboard:
|
||||
for button in range(len(row)):
|
||||
row[button] = row[button].to_python()
|
||||
MessageMarkup.objects.create(
|
||||
message_id=msg_with_markup.message_id,
|
||||
chat_id=msg_with_markup.chat.id,
|
||||
buttons=ujson.dumps(markup.inline_keyboard)
|
||||
)
|
||||
else:
|
||||
await bot.send_message(msg.chat.id,
|
||||
'У Вас нет связанных чатов. Чтобы привязать чат, добавьте бота в группу, а если бот уже добавлен - используйте команду /dialogs')
|
||||
# TODO: /leave chat
|
||||
# TODO: manage forwards
|
||||
|
||||
|
||||
@dp.message_handler(commands=['help'])
|
||||
async def help_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
HELP_MESSAGE = '/start - Логин в Вконтакте\n' \
|
||||
'/dialogs /d - Список диалогов\n' \
|
||||
'/read /r - Прочесть диалог ВКонтакте\n' \
|
||||
'/search /s - Поиск по диалогам\n' \
|
||||
'/chat - Список связанных чатов с диалогами ВКонтакте, привязать чат к диалогу можно добавив бота в группу\n' \
|
||||
'/stop - Выход из ВКонтакте' \
|
||||
'/help - Помощь'
|
||||
|
||||
await bot.send_message(msg.chat.id, HELP_MESSAGE, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['text'])
|
||||
async def handle_text(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if msg.chat.type == 'private':
|
||||
m = oauth_link.search(msg.text)
|
||||
if m:
|
||||
token = m.group(1)
|
||||
if not VkUser.objects.filter(token=token).exists():
|
||||
try:
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(session)
|
||||
vkuserinfo = await api('account.getProfileInfo', name_case='gen')
|
||||
vkuser, vkuser_created = VkUser.objects.update_or_create(
|
||||
defaults={'token': token, 'is_polling': True}, owner=user)
|
||||
existing_polling = next((task for task in TASKS if task['token'] == vkuser.token), None)
|
||||
if existing_polling:
|
||||
existing_polling['task'].cancel()
|
||||
driver = DRIVERS.get(vkuser.token, '')
|
||||
if driver:
|
||||
driver.close()
|
||||
TASKS.append({'token': vkuser.token, 'task': asyncio.ensure_future(vk_polling(vkuser))})
|
||||
await msg.reply(
|
||||
'Вход выполнен в аккаунт {} {}!\n[Использование](https://asergey.me/tgvkbot/usage/)'.format(
|
||||
vkuserinfo['first_name'], vkuserinfo.get('last_name', '')), parse_mode='Markdown')
|
||||
except VkAuthError:
|
||||
await msg.reply('Неверная ссылка, попробуйте ещё раз!')
|
||||
else:
|
||||
await msg.reply('Вход уже выполнен!\n/stop для выхода.')
|
||||
return
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
if msg.reply_to_message and msg.reply_to_message.text == 'Поиск беседы 🔍':
|
||||
if msg.chat.type == 'private' or not Message.objects.filter(tg_id=msg.reply_to_message.message_id,
|
||||
tg_chat=msg.reply_to_message.chat.id).exists():
|
||||
await search_dialogs(msg, user)
|
||||
return
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
forward_messages_exists, message = await is_forwarding(msg.text)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
if message_options != {}:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['contact'])
|
||||
async def handle_contact(msg: types.Message):
|
||||
new_text = msg.contact.first_name
|
||||
if msg.contact.last_name:
|
||||
new_text += ' ' + msg.contact.last_name
|
||||
new_text += '\n'
|
||||
new_text += msg.contact.phone_number
|
||||
msg.text = new_text
|
||||
await handle_text(msg)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['photo'])
|
||||
async def handle_photo(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
file_id = msg.photo[-1].file_id
|
||||
if message_options:
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
attachment_type='photo',
|
||||
upload_field='photo',
|
||||
upload_method='photos.getMessagesUploadServer',
|
||||
on_server_field='photo',
|
||||
save_method='photos.saveMessagesPhoto')
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['document', 'voice', 'audio', 'sticker'])
|
||||
async def handle_documents(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if tgchat:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
else:
|
||||
forward = None
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
file_id = getattr(msg, msg.content_type).file_id
|
||||
if message_options:
|
||||
upload_attachment_options = {
|
||||
'attachment_type': 'doc',
|
||||
'upload_field': 'file',
|
||||
'upload_method': 'docs.getUploadServer',
|
||||
'on_server_field': 'file',
|
||||
'save_method': 'docs.save',
|
||||
}
|
||||
if hasattr(getattr(msg, msg.content_type), 'file_name') and getattr(msg, msg.content_type).file_name:
|
||||
upload_attachment_options['title'] = getattr(msg, msg.content_type).file_name
|
||||
|
||||
if msg.content_type == 'voice':
|
||||
upload_attachment_options['upload_type'] = 'audio_message'
|
||||
|
||||
if msg.content_type == 'sticker':
|
||||
upload_attachment_options['upload_type'] = 'graffiti'
|
||||
upload_attachment_options['rewrite_name'] = True
|
||||
upload_attachment_options['default_name'] = 'graffiti.png'
|
||||
|
||||
if msg.content_type == 'audio':
|
||||
audioname = ''
|
||||
if msg.audio.performer and msg.audio.title:
|
||||
audioname += msg.audio.performer + ' - ' + msg.audio.title
|
||||
elif msg.audio.performer:
|
||||
audioname += msg.audio.performer
|
||||
elif msg.audio.title:
|
||||
audioname += msg.audio.title
|
||||
else:
|
||||
audioname = f'tgvkbot_audio_{file_id}'
|
||||
upload_attachment_options['title'] = audioname
|
||||
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
**upload_attachment_options)
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['video', 'video_note'])
|
||||
async def handle_videos(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if tgchat:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
else:
|
||||
forward = None
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
file_id = getattr(msg, msg.content_type).file_id
|
||||
if message_options:
|
||||
upload_attachment_options = {
|
||||
'attachment_type': 'video',
|
||||
'upload_field': 'video_file',
|
||||
'upload_method': 'video.save',
|
||||
}
|
||||
if hasattr(getattr(msg, msg.content_type), 'file_name') and getattr(msg, msg.content_type).file_name:
|
||||
upload_attachment_options['title'] = getattr(msg, msg.content_type).file_name
|
||||
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
**upload_attachment_options)
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['new_chat_members'], func=is_bot_in_iterator)
|
||||
async def handle_join(msg: types.Message, edit=False, chat_id=None, message_id=None, text='', exclude=False):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
await bot.send_chat_action(msg.chat.id, 'typing')
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
pages = None
|
||||
if vk_user:
|
||||
if forward:
|
||||
text = text or '<i>Этот чат уже привязан к диалогу ВКонтакте, Вы можете выбрать новый диалог</i>'
|
||||
else:
|
||||
text = text or '<i>Выберите диалог ВКонтакте к которому будет привязан этот чат</i>'
|
||||
markup = InlineKeyboardMarkup()
|
||||
excluded_ids = []
|
||||
if exclude:
|
||||
excluded_ids = [forward.vkchat.cid for forward in Forward.objects.filter(owner=user)]
|
||||
pages = await get_dialogs(vk_user.token, excluded_ids)
|
||||
if pages:
|
||||
for buttons_row in pages[0]:
|
||||
markup.row(*buttons_row)
|
||||
await get_pages_switcher(markup, 0, pages)
|
||||
else:
|
||||
me = await bot.me
|
||||
text = '<i>Вход не выполнен! Сперва нужно выполнить вход в ВК через бота</i>'
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('ВХОД', url=f'https//t.me/{me.username}?start=login'),
|
||||
InlineKeyboardButton('✅ Я залогинился', callback_data=f'logged-{msg.from_user.id}'))
|
||||
if edit:
|
||||
msg_with_markup = await bot.edit_message_text(text=text, chat_id=chat_id, message_id=message_id,
|
||||
reply_markup=markup, parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
msg_with_markup = await bot.send_message(msg.chat.id, text=text, reply_markup=markup, parse_mode=ParseMode.HTML)
|
||||
if pages:
|
||||
for page in pages:
|
||||
for row in page:
|
||||
for button in range(len(row)):
|
||||
row[button] = row[button].to_python()
|
||||
|
||||
MessageMarkup.objects.create(
|
||||
message_id=msg_with_markup.message_id,
|
||||
chat_id=msg_with_markup.chat.id,
|
||||
buttons=ujson.dumps(pages)
|
||||
)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=types.ContentType.ANY, func=lambda msg: msg.group_chat_created is True)
|
||||
async def handle_new_group(msg: types.Message):
|
||||
await handle_join(msg)
|
||||
|
||||
|
||||
@dp.message_handler(func=lambda msg: msg.migrate_to_chat_id is not None)
|
||||
async def handle_chat_migration(msg: types.Message):
|
||||
forwards = Forward.objects.filter(tgchat__cid=msg.migrate_from_chat_id)
|
||||
for forward in forwards:
|
||||
forward.tgchat.cid = msg.migrate_to_chat_id
|
||||
forward.tgchat.save()
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
webhook = await bot.get_webhook_info()
|
||||
|
||||
# If URL is bad
|
||||
if webhook.url != WEBHOOK_URL:
|
||||
# If URL doesnt match current - remove webhook
|
||||
if not webhook.url:
|
||||
await bot.delete_webhook()
|
||||
|
||||
# Set new URL for webhook
|
||||
await bot.set_webhook(WEBHOOK_URL)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TASKS = vk_polling_tasks()
|
||||
asyncio.gather(*[task['task'] for task in TASKS])
|
||||
app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT)
|
897
vk_messages.py
Normal file
897
vk_messages.py
Normal file
@ -0,0 +1,897 @@
|
||||
from concurrent.futures._base import CancelledError
|
||||
|
||||
from aiovk.longpoll import LongPoll
|
||||
|
||||
from bot import *
|
||||
|
||||
log = logging.getLogger('vk_messages')
|
||||
|
||||
|
||||
################### Честно взято по лицензии https://github.com/vk-brain/sketal/blob/master/LICENSE ###################
|
||||
|
||||
def parse_msg_flags(bitmask, keys=('unread', 'outbox', 'replied', 'important', 'chat',
|
||||
'friends', 'spam', 'deleted', 'fixed', 'media', 'hidden')):
|
||||
"""Функция для чтения битовой маски и возврата словаря значений"""
|
||||
|
||||
start = 1
|
||||
values = []
|
||||
for _ in range(1, 12):
|
||||
result = bitmask & start
|
||||
start *= 2
|
||||
values.append(bool(result))
|
||||
return dict(zip(keys, values))
|
||||
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Wait(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
CUSTOM = 2
|
||||
|
||||
|
||||
class EventType(Enum):
|
||||
Longpoll = 0
|
||||
ChatChange = 1
|
||||
Callback = 2
|
||||
|
||||
|
||||
class Event:
|
||||
__slots__ = ("api", "type", "reserved_by", "occupied_by", "meta")
|
||||
|
||||
def __init__(self, api, evnt_type):
|
||||
self.api = api
|
||||
self.type = evnt_type
|
||||
|
||||
self.meta = {}
|
||||
|
||||
self.reserved_by = []
|
||||
self.occupied_by = []
|
||||
|
||||
|
||||
# https://vk.com/dev/using_longpoll
|
||||
class LongpollEvent(Event):
|
||||
__slots__ = ("evnt_data", "id")
|
||||
|
||||
def __init__(self, api, evnt_id, evnt_data):
|
||||
super().__init__(api, EventType.Longpoll)
|
||||
|
||||
self.id = evnt_id
|
||||
self.evnt_data = evnt_data
|
||||
|
||||
def __str__(self):
|
||||
return f"LongpollEvent ({self.id}, {self.evnt_data[1] if len(self.evnt_data) > 1 else '_'})"
|
||||
|
||||
|
||||
class MessageEventData(object):
|
||||
__slots__ = ("is_multichat", "user_id", "full_text", "full_message_data",
|
||||
"time", "msg_id", "attaches", "is_out", "forwarded", "chat_id",
|
||||
"true_user_id", "is_forwarded", "true_msg_id")
|
||||
|
||||
@staticmethod
|
||||
def from_message_body(obj):
|
||||
data = MessageEventData()
|
||||
|
||||
data.attaches = {}
|
||||
data.forwarded = []
|
||||
|
||||
c = 0
|
||||
for a in obj.get("attachments", []):
|
||||
c += 1
|
||||
|
||||
data.attaches[f'attach{c}_type'] = a['type']
|
||||
try:
|
||||
data.attaches[f'attach{c}'] = f'{a[a["type"]]["owner_id"]}_{a[a["type"]]["id"]}'
|
||||
except KeyError:
|
||||
data.attaches[f'attach{c}'] = ""
|
||||
|
||||
if 'fwd_messages' in obj:
|
||||
data.forwarded = MessageEventData.parse_brief_forwarded_messages(obj)
|
||||
|
||||
if "chat_id" in obj:
|
||||
data.is_multichat = True
|
||||
data.chat_id = int(obj["chat_id"])
|
||||
|
||||
if "id" in obj:
|
||||
data.msg_id = obj["id"]
|
||||
data.true_msg_id = obj["id"]
|
||||
|
||||
data.user_id = int(obj['user_id'])
|
||||
data.true_user_id = int(obj['user_id'])
|
||||
data.full_text = obj['body']
|
||||
data.time = int(obj['date'])
|
||||
data.is_out = obj.get('out', False)
|
||||
data.is_forwarded = False
|
||||
data.full_message_data = obj
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def parse_brief_forwarded_messages(obj):
|
||||
if 'fwd_messages' not in obj:
|
||||
return ()
|
||||
|
||||
result = []
|
||||
|
||||
for mes in obj['fwd_messages']:
|
||||
result.append((mes.get('id', None), MessageEventData.parse_brief_forwarded_messages(mes)))
|
||||
|
||||
return tuple(result)
|
||||
|
||||
@staticmethod
|
||||
def parse_brief_forwarded_messages_from_lp(data):
|
||||
result = []
|
||||
|
||||
token = ""
|
||||
i = -1
|
||||
while True:
|
||||
i += 1
|
||||
|
||||
if i >= len(data):
|
||||
if token:
|
||||
result.append((token, ()))
|
||||
|
||||
break
|
||||
|
||||
if data[i] in "1234567890_-":
|
||||
token += data[i]
|
||||
continue
|
||||
|
||||
if data[i] in (",", ")"):
|
||||
if not token:
|
||||
continue
|
||||
|
||||
result.append((token, ()))
|
||||
token = ""
|
||||
continue
|
||||
|
||||
if data[i] == ":":
|
||||
stack = 1
|
||||
|
||||
for j in range(i + 2, len(data)):
|
||||
if data[j] == "(":
|
||||
stack += 1
|
||||
|
||||
elif data[j] == ")":
|
||||
stack -= 1
|
||||
|
||||
if stack == 0:
|
||||
jump_to_i = j
|
||||
break
|
||||
|
||||
sub_data = data[i + 2: jump_to_i]
|
||||
|
||||
result.append((token, MessageEventData.parse_brief_forwarded_messages_from_lp(sub_data)))
|
||||
|
||||
i = jump_to_i + 1
|
||||
token = ""
|
||||
continue
|
||||
|
||||
return tuple(result)
|
||||
|
||||
def __init__(self):
|
||||
self.is_multichat = False
|
||||
self.is_forwarded = False
|
||||
self.is_out = False
|
||||
|
||||
self.chat_id = 0
|
||||
self.user_id = 0
|
||||
self.true_user_id = 0
|
||||
self.full_text = ""
|
||||
self.time = ""
|
||||
self.msg_id = 0
|
||||
self.true_msg_id = 0
|
||||
self.attaches = None
|
||||
self.forwarded = None
|
||||
self.full_message_data = None
|
||||
|
||||
|
||||
class Attachment(object):
|
||||
__slots__ = ('type', 'owner_id', 'id', 'access_key', 'url', 'ext')
|
||||
|
||||
def __init__(self, attach_type, owner_id, aid, access_key=None, url=None, ext=None):
|
||||
self.type = attach_type
|
||||
self.owner_id = owner_id
|
||||
self.id = aid
|
||||
self.access_key = access_key
|
||||
self.url = url
|
||||
self.ext = ext
|
||||
|
||||
@staticmethod
|
||||
def from_upload_result(result, attach_type="photo"):
|
||||
url = None
|
||||
|
||||
for k in result:
|
||||
if "photo_" in k:
|
||||
url = result[k]
|
||||
elif "link_" in k:
|
||||
url = result[k]
|
||||
elif "url" == k:
|
||||
url = result[k]
|
||||
|
||||
return Attachment(attach_type, result["owner_id"], result["id"], url=url, ext=result.get("ext"))
|
||||
|
||||
@staticmethod
|
||||
def from_raw(raw_attach):
|
||||
a_type = raw_attach['type']
|
||||
attach = raw_attach[a_type]
|
||||
|
||||
url = None
|
||||
|
||||
for k, v in attach.items():
|
||||
if "photo_" in k:
|
||||
url = v
|
||||
elif "link_" in k:
|
||||
url = v
|
||||
elif "url" == k:
|
||||
url = v
|
||||
|
||||
return Attachment(a_type, attach.get('owner_id', ''), attach.get('id', ''), attach.get('access_key'), url,
|
||||
ext=attach.get("ext"))
|
||||
|
||||
def value(self):
|
||||
if self.access_key:
|
||||
return f'{self.type}{self.owner_id}_{self.id}_{self.access_key}'
|
||||
|
||||
return f'{self.type}{self.owner_id}_{self.id}'
|
||||
|
||||
def __str__(self):
|
||||
return self.value()
|
||||
|
||||
|
||||
MAX_LENGHT = 4000
|
||||
|
||||
from math import ceil
|
||||
|
||||
|
||||
class LPMessage(object):
|
||||
"""Класс, объект которого передаётся в плагин для упрощённого ответа"""
|
||||
|
||||
__slots__ = ('message_data', 'api', 'is_multichat', 'chat_id', 'user_id', 'is_out', 'true_user_id',
|
||||
'timestamp', 'answer_values', 'msg_id', 'text', 'full_text', 'meta', 'is_event',
|
||||
'brief_attaches', 'brief_forwarded', '_full_attaches', '_full_forwarded',
|
||||
'reserved_by', 'occupied_by', 'peer_id', "is_forwarded", 'true_msg_id')
|
||||
|
||||
def __init__(self, vk_api_object, message_data):
|
||||
self.message_data = message_data
|
||||
self.api = vk_api_object
|
||||
|
||||
self.reserved_by = []
|
||||
self.occupied_by = []
|
||||
self.meta = {}
|
||||
|
||||
self.is_event = False
|
||||
self.is_multichat = message_data.is_multichat
|
||||
self.is_forwarded = message_data.is_forwarded
|
||||
|
||||
self.user_id = message_data.user_id
|
||||
self.true_user_id = message_data.true_user_id
|
||||
self.chat_id = message_data.chat_id
|
||||
self.peer_id = (message_data.chat_id or message_data.user_id) + self.is_multichat * 2000000000
|
||||
self.full_text = message_data.full_text
|
||||
self.text = self.full_text.replace(""", "\"") # Not need .lower() there # edited by @Kylmakalle
|
||||
|
||||
self.msg_id = message_data.msg_id
|
||||
self.true_msg_id = message_data.true_msg_id
|
||||
self.is_out = message_data.is_out
|
||||
|
||||
self.timestamp = message_data.time
|
||||
|
||||
self.brief_forwarded = message_data.forwarded
|
||||
self._full_forwarded = None
|
||||
self.brief_attaches = message_data.attaches
|
||||
self._full_attaches = None
|
||||
|
||||
if self.is_multichat:
|
||||
self.answer_values = {'chat_id': self.chat_id}
|
||||
|
||||
else:
|
||||
self.answer_values = {'user_id': self.user_id}
|
||||
|
||||
async def get_full_attaches(self):
|
||||
"""Get list of all attachments as `Attachment` for this message"""
|
||||
|
||||
if self._full_attaches is None:
|
||||
await self.get_full_data()
|
||||
|
||||
return self._full_attaches
|
||||
|
||||
async def get_full_forwarded(self):
|
||||
"""Get list of all forwarded messages as `LPMessage` for this message"""
|
||||
|
||||
if self._full_forwarded is None:
|
||||
await self.get_full_data()
|
||||
|
||||
return self._full_forwarded
|
||||
|
||||
async def get_full_data(self, message_data=None):
|
||||
"""Update lists of all forwarded messages and all attachments for this message"""
|
||||
|
||||
self._full_attaches = []
|
||||
self._full_forwarded = []
|
||||
|
||||
if not message_data:
|
||||
values = {'message_ids': self.msg_id}
|
||||
|
||||
full_message_data = await self.api.messages.getById(**values)
|
||||
|
||||
if not full_message_data or not full_message_data['items']: # Если пришёл пустой ответ от VK API
|
||||
return
|
||||
|
||||
message = full_message_data['items'][0]
|
||||
|
||||
else:
|
||||
message = message_data
|
||||
|
||||
if "attachments" in message:
|
||||
for raw_attach in message["attachments"]:
|
||||
attach = Attachment.from_raw(raw_attach) # Создаём аттач
|
||||
|
||||
self._full_attaches.append(attach) # Добавляем к нашему внутреннему списку аттачей
|
||||
|
||||
if 'fwd_messages' in message:
|
||||
self._full_forwarded, self.brief_forwarded = await self.parse_forwarded_messages(message)
|
||||
|
||||
async def parse_forwarded_messages(self, im):
|
||||
if 'fwd_messages' not in im:
|
||||
return (), ()
|
||||
|
||||
result = []
|
||||
brief_result = []
|
||||
|
||||
for mes in im['fwd_messages']:
|
||||
obj = MessageEventData.from_message_body(mes)
|
||||
|
||||
obj.msg_id = self.msg_id
|
||||
obj.chat_id = self.chat_id
|
||||
obj.user_id = self.user_id
|
||||
obj.is_multichat = self.is_multichat
|
||||
obj.is_out = self.is_out
|
||||
obj.is_forwarded = True
|
||||
|
||||
m = await LPMessage.create(self.api, obj)
|
||||
|
||||
big_result, small_result = await self.parse_forwarded_messages(mes)
|
||||
|
||||
result.append((m, big_result))
|
||||
brief_result.append((m.msg_id, small_result))
|
||||
|
||||
return tuple(result), tuple(brief_result)
|
||||
|
||||
@staticmethod
|
||||
def prepare_message(message):
|
||||
"""Split message to parts that can be send by `messages.send`"""
|
||||
|
||||
message_length = len(message)
|
||||
|
||||
if message_length <= MAX_LENGHT:
|
||||
return [message]
|
||||
|
||||
def fit_parts(sep):
|
||||
current_length = 0
|
||||
current_message = ""
|
||||
|
||||
sep_length = len(sep)
|
||||
parts = message.split(sep)
|
||||
length = len(parts)
|
||||
|
||||
for j in range(length):
|
||||
m = parts[j]
|
||||
temp_length = len(m)
|
||||
|
||||
if temp_length > MAX_LENGHT:
|
||||
return
|
||||
|
||||
if j != length - 1 and current_length + temp_length + sep_length <= MAX_LENGHT:
|
||||
current_message += m + sep
|
||||
current_length += temp_length + sep_length
|
||||
|
||||
elif current_length + temp_length <= MAX_LENGHT:
|
||||
current_message += m
|
||||
current_length += temp_length
|
||||
|
||||
elif current_length + temp_length > MAX_LENGHT:
|
||||
yield current_message
|
||||
|
||||
current_length = temp_length
|
||||
current_message = m
|
||||
|
||||
if j != length - 1 and current_length + sep_length < MAX_LENGHT:
|
||||
current_message += sep
|
||||
current_length += sep_length
|
||||
|
||||
if current_message:
|
||||
yield current_message
|
||||
|
||||
result = list(fit_parts("\n"))
|
||||
|
||||
if not result:
|
||||
result = list(fit_parts(" "))
|
||||
|
||||
if not result:
|
||||
result = []
|
||||
|
||||
for i in range(int(ceil(message_length / MAX_LENGHT))):
|
||||
result.append(message[i * MAX_LENGHT: (i + 1) * MAX_LENGHT])
|
||||
|
||||
return result
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
async def create(vk_api_object, data):
|
||||
msg = LPMessage(vk_api_object, data)
|
||||
|
||||
if data.full_message_data:
|
||||
await msg.get_full_data(data.full_message_data)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
class ChatChangeEvent(Event):
|
||||
__slots__ = ("source_act", "source_mid", "chat_id", "new_title",
|
||||
"old_title", "changer", "chat_id", "new_cover", "user_id")
|
||||
|
||||
def __init__(self, api, user_id, chat_id, source_act, source_mid, new_title, old_title, new_cover, changer):
|
||||
super().__init__(api, EventType.ChatChange)
|
||||
|
||||
self.chat_id = chat_id
|
||||
self.user_id = user_id
|
||||
|
||||
self.source_act = source_act
|
||||
self.source_mid = source_mid
|
||||
|
||||
self.new_cover = new_cover
|
||||
|
||||
self.new_title = new_title
|
||||
self.old_title = old_title
|
||||
self.changer = changer
|
||||
|
||||
|
||||
async def check_event(api, user_id, chat_id, attaches):
|
||||
if chat_id != 0 and "source_act" in attaches:
|
||||
photo = attaches.get("attach1_type") + attaches.get("attach1") if "attach1" in attaches else None
|
||||
|
||||
evnt = ChatChangeEvent(api, user_id, chat_id, attaches.get("source_act"),
|
||||
int(attaches.get("source_mid", 0)), attaches.get("source_text"),
|
||||
attaches.get("source_old_text"), photo, int(attaches.get("from", 0)))
|
||||
|
||||
await process_event(evnt)
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def process_longpoll_event(api, new_event):
|
||||
if not new_event:
|
||||
return
|
||||
|
||||
event_id = new_event[0]
|
||||
|
||||
if event_id != 4 and event_id != 5:
|
||||
evnt = LongpollEvent(api, event_id, new_event)
|
||||
|
||||
return # await process_event(evnt)
|
||||
|
||||
data = MessageEventData()
|
||||
data.msg_id = new_event[1]
|
||||
data.attaches = new_event[6]
|
||||
data.time = int(new_event[4])
|
||||
|
||||
try:
|
||||
data.user_id = int(data.attaches['from'])
|
||||
data.chat_id = int(new_event[3]) - 2000000000
|
||||
data.is_multichat = True
|
||||
|
||||
del data.attaches['from']
|
||||
|
||||
except KeyError:
|
||||
data.user_id = int(new_event[3])
|
||||
data.is_multichat = False
|
||||
|
||||
# https://vk.com/dev/using_longpoll_2
|
||||
flags = parse_msg_flags(new_event[2])
|
||||
|
||||
if flags['outbox']:
|
||||
return
|
||||
|
||||
data.is_out = True
|
||||
|
||||
data.full_text = new_event[5].replace('<br>', '\n')
|
||||
|
||||
if "fwd" in data.attaches:
|
||||
data.forwarded = MessageEventData.parse_brief_forwarded_messages_from_lp(data.attaches["fwd"])
|
||||
del data.attaches["fwd"]
|
||||
|
||||
else:
|
||||
data.forwarded = []
|
||||
|
||||
msg = LPMessage(api, data)
|
||||
|
||||
if await check_event(api, data.user_id, data.chat_id, data.attaches):
|
||||
msg.is_event = True
|
||||
|
||||
await process_message(msg)
|
||||
|
||||
|
||||
#######################################################################################################################
|
||||
|
||||
|
||||
async def process_message(msg, token=None, is_multichat=None, vk_chat_id=None, user_id=None, forward_settings=None,
|
||||
vkchat=None,
|
||||
full_msg=None, forwarded=False, vk_msg_id=None, main_message=None, known_users=None):
|
||||
token = token or msg.api._session.access_token
|
||||
is_multichat = is_multichat or msg.is_multichat
|
||||
vk_msg_id = vk_msg_id or msg.msg_id
|
||||
user_id = user_id or msg.user_id
|
||||
known_users = known_users or {}
|
||||
|
||||
vkuser = VkUser.objects.filter(token=token).first()
|
||||
if not vkuser:
|
||||
return
|
||||
|
||||
if user_id not in known_users or {}:
|
||||
peer_id, first_name, last_name = await get_name(user_id, msg.api)
|
||||
known_users[user_id] = (peer_id, first_name, last_name)
|
||||
else:
|
||||
peer_id, first_name, last_name = known_users[user_id]
|
||||
|
||||
if is_multichat:
|
||||
vk_chat_id = vk_chat_id or msg.peer_id
|
||||
else:
|
||||
vk_chat_id = vk_chat_id or peer_id
|
||||
|
||||
if not vkchat:
|
||||
vkchat, created_vkchat = await get_vk_chat(vk_chat_id)
|
||||
forward_setting = forward_settings or Forward.objects.filter(owner=vkuser.owner, vkchat=vkchat).first()
|
||||
|
||||
full_msg = full_msg or await msg.api('messages.getById', message_ids=', '.join(str(x) for x in [vk_msg_id]))
|
||||
if full_msg.get('items'):
|
||||
for vk_msg in full_msg['items']:
|
||||
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
|
||||
vk_msg['attachments']]
|
||||
if vk_msg.get('geo'):
|
||||
location = vk_msg['geo']['coordinates'].split(' ')
|
||||
is_venue = vk_msg['geo'].get('place')
|
||||
if is_venue:
|
||||
attaches_scheme.append({'content': [location[0], location[1], is_venue.get('title', 'Место'),
|
||||
is_venue.get('city', 'Город')], 'type': 'venue'})
|
||||
else:
|
||||
attaches_scheme.append({'content': [location[0], location[1]], 'type': 'location'})
|
||||
name = first_name + ((' ' + last_name) if last_name else '')
|
||||
if forward_setting:
|
||||
if forwarded or is_multichat:
|
||||
header = f'<b>{name}</b>' + '\n'
|
||||
if not forwarded:
|
||||
header = ''
|
||||
to_tg_chat = forward_setting.tgchat.cid
|
||||
else:
|
||||
if forwarded or not is_multichat:
|
||||
header = f'<b>{name}</b>' + '\n'
|
||||
elif is_multichat:
|
||||
header = f'<b>{name} @ {vk_msg["title"]}</b>' + '\n'
|
||||
to_tg_chat = vkuser.owner.uid
|
||||
|
||||
body_parts = []
|
||||
body = vk_msg.get('body', '')
|
||||
if body:
|
||||
if (len(header) + len(body)) > MAX_MESSAGE_LENGTH:
|
||||
body_parts = safe_split_text(header + body, MAX_MESSAGE_LENGTH)
|
||||
body_parts[-1] = body_parts[-1] + '\n'
|
||||
else:
|
||||
body += '\n'
|
||||
|
||||
if attaches_scheme:
|
||||
first_text_attach = next((attach for attach in attaches_scheme if attach and attach['type'] == 'text'),
|
||||
None)
|
||||
if first_text_attach:
|
||||
if body_parts and (len(first_text_attach) + len(body_parts[-1])) > MAX_MESSAGE_LENGTH:
|
||||
body_parts.append(first_text_attach['content'])
|
||||
else:
|
||||
body += first_text_attach['content']
|
||||
attaches_scheme.remove(first_text_attach)
|
||||
|
||||
if body_parts:
|
||||
for body_part in range(len(body_parts)):
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.TYPING)
|
||||
tg_message = await bot.send_message(vkuser.owner.uid, body_parts[body_part],
|
||||
parse_mode=ParseMode.HTML,
|
||||
reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
Message.objects.create(
|
||||
vk_chat=vk_chat_id,
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
elif not body_parts and (header + body):
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.TYPING)
|
||||
tg_message = await bot.send_message(to_tg_chat, header + body, parse_mode=ParseMode.HTML,
|
||||
reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
Message.objects.create(
|
||||
vk_chat=vk_chat_id,
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
|
||||
photo_attachments = [attach for attach in attaches_scheme if attach and attach['type'] == 'photo']
|
||||
|
||||
if len(photo_attachments) > 1:
|
||||
media = MediaGroup()
|
||||
for photo in photo_attachments:
|
||||
media.attach_photo(photo['content'])
|
||||
tg_messages = await tgsend(bot.send_media_group, to_tg_chat, media, reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
for tg_message in tg_messages:
|
||||
Message.objects.create(
|
||||
vk_chat=vk_chat_id,
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
|
||||
for attachment in attaches_scheme:
|
||||
if attachment:
|
||||
if attachment['type'] == 'text':
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.TYPING)
|
||||
tg_message = await tgsend(bot.send_message, to_tg_chat, attachment['content'],
|
||||
parse_mode=ParseMode.HTML, reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
elif attachment['type'] == 'photo' and len(photo_attachments) == 1:
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.UPLOAD_PHOTO)
|
||||
tg_message = await tgsend(bot.send_photo, to_tg_chat, attachment['content'],
|
||||
reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
elif attachment['type'] == 'document':
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.UPLOAD_DOCUMENT)
|
||||
tg_message = await tgsend(bot.send_document, to_tg_chat,
|
||||
attachment.get('content', '') or attachment.get('url'),
|
||||
reply_to_message_id=main_message, disable_notification=disable_notify)
|
||||
if 'content' in attachment:
|
||||
attachment['content'].close()
|
||||
os.remove(os.path.join(attachment['temp_path'],
|
||||
attachment['file_name'] + attachment['custom_ext']))
|
||||
elif attachment['type'] == 'video':
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.UPLOAD_VIDEO)
|
||||
tg_message = await tgsend(bot.send_video, to_tg_chat, attachment['content'],
|
||||
reply_to_message_id=main_message, disable_notification=disable_notify)
|
||||
elif attachment['type'] == 'sticker':
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.TYPING)
|
||||
tg_message = await tgsend(bot.send_sticker, to_tg_chat, attachment['content'],
|
||||
reply_to_message_id=main_message, disable_notification=disable_notify)
|
||||
elif attachment['type'] == 'location':
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.FIND_LOCATION)
|
||||
tg_message = await tgsend(bot.send_location, to_tg_chat, *attachment['content'],
|
||||
reply_to_message_id=main_message, disable_notification=disable_notify)
|
||||
elif attachment['type'] == 'venue':
|
||||
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)
|
||||
|
||||
Message.objects.create(
|
||||
vk_chat=vk_chat_id,
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
if vk_msg.get('fwd_messages'):
|
||||
await bot.send_chat_action(to_tg_chat, ChatActions.TYPING)
|
||||
fwd_ptr = tg_message = await bot.send_message(vkuser.owner.uid, header + '<i>Пересланные сообщения</i>',
|
||||
parse_mode=ParseMode.HTML,
|
||||
reply_to_message_id=main_message,
|
||||
disable_notification=disable_notify)
|
||||
Message.objects.create(
|
||||
vk_chat=vk_chat_id,
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
|
||||
for fwd_message in vk_msg['fwd_messages']:
|
||||
await process_message(msg, token=token, is_multichat=is_multichat, vk_chat_id=vk_chat_id,
|
||||
user_id=fwd_message['user_id'],
|
||||
forward_settings=forward_settings, vk_msg_id=vk_msg_id, vkchat=vkchat,
|
||||
full_msg={'items': [fwd_message]}, forwarded=True,
|
||||
main_message=fwd_ptr.message_id, known_users=known_users)
|
||||
|
||||
|
||||
async def get_name(identifier, api):
|
||||
if identifier > 0:
|
||||
peer = await api('users.get', user_ids=identifier)
|
||||
first_name = peer[0]['first_name']
|
||||
last_name = peer[0]['last_name'] or ''
|
||||
else:
|
||||
peer = await api('groups.getById', group_ids=abs(identifier))
|
||||
first_name = peer[0]['name']
|
||||
last_name = ''
|
||||
peer[0]['id'] = -peer[0]['id']
|
||||
return peer[0]['id'], first_name, last_name
|
||||
|
||||
|
||||
async def tgsend(method, *args, **kwargs):
|
||||
try:
|
||||
tg_message = await method(*args, **kwargs)
|
||||
return tg_message
|
||||
except RetryAfter as e:
|
||||
asyncio.sleep(e.timeout)
|
||||
tgsend(method, *args, **kwargs)
|
||||
except Exception:
|
||||
log.exception(msg='Error in message sending', exc_info=True)
|
||||
|
||||
|
||||
async def process_event(msg):
|
||||
pass
|
||||
|
||||
|
||||
async def process_attachment(attachment):
|
||||
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
|
||||
|
||||
elif atype == 'video':
|
||||
title = attachment[atype]['title']
|
||||
owner_id = attachment[atype]['owner_id']
|
||||
video_id = attachment[atype]['id']
|
||||
access_key = attachment[atype].get('access_key')
|
||||
video_url = f'https://vk.com/video{owner_id}_{video_id}' + f'_{access_key}' if access_key else ''
|
||||
return {'content': f'<i>🎥 Видеозапись</i> <a href="{video_url}">{title}</a>', 'type': 'text'}
|
||||
|
||||
elif atype == 'doc':
|
||||
ext = attachment[atype]['ext']
|
||||
if ext == 'gif':
|
||||
size = attachment[atype]['preview']['video']['file_size']
|
||||
gif_url = attachment[atype]['url'] + '&mp4=1'
|
||||
if size > MAX_FILE_SIZE:
|
||||
return {'content': f'<a href="{gif_url}">GIF</a>', 'type': 'text'}
|
||||
return {'content': gif_url, 'type': 'document'}
|
||||
elif 'preview' in attachment[atype] and attachment[atype]['preview'].get('graffiti'):
|
||||
graffiti_url = attachment[atype]['preview']['photo']['sizes'][-1]['src']
|
||||
with aiohttp.ClientSession() as session:
|
||||
img = await (await session.request('GET', graffiti_url)).read()
|
||||
imgdata = Image.open(io.BytesIO(img))
|
||||
webp = io.BytesIO()
|
||||
imgdata.save(webp, format='WebP')
|
||||
file_bytes = webp.getvalue()
|
||||
return {'content': file_bytes, 'type': 'sticker'}
|
||||
else:
|
||||
size = attachment[atype]['size']
|
||||
doc_url = attachment[atype]['url'] # + f'&{ext}=1'
|
||||
docname = attachment[atype].get('title', 'Документ')
|
||||
if size > MAX_FILE_SIZE:
|
||||
return {'content': f'<a href="{doc_url}">📄 {docname}</a>', 'type': 'text'}
|
||||
content = await get_content(doc_url, docname)
|
||||
# supported_exts = ['zip', 'pdf', 'jpg', 'png', 'doc', 'docx']
|
||||
if 'content' in content:
|
||||
content['type'] = 'document'
|
||||
return content
|
||||
else:
|
||||
return {'content': f'<a href="{doc_url}">📄 {content["docname"]}</a>', 'type': 'text'}
|
||||
|
||||
elif atype == 'sticker':
|
||||
sticker_url = attachment[atype][await get_max_photo(attachment[atype])]
|
||||
with aiohttp.ClientSession() as session:
|
||||
img = await (await session.request('GET', sticker_url)).read()
|
||||
imgdata = Image.open(io.BytesIO(img))
|
||||
webp = io.BytesIO()
|
||||
imgdata.save(webp, format='WebP')
|
||||
file_bytes = webp.getvalue()
|
||||
return {'content': file_bytes, 'type': 'sticker'}
|
||||
|
||||
elif atype == 'gift':
|
||||
gift_url = attachment[atype][await get_max_photo(attachment[atype], 'thumb')]
|
||||
return {'content': f'<a href="{gift_url}">Подарок</a>', 'type': 'text'}
|
||||
elif atype == 'link':
|
||||
link_url = attachment[atype]['url']
|
||||
link_name = attachment[atype].get('title', '')
|
||||
if link_name:
|
||||
link_name += '\n'
|
||||
link_name += attachment[atype].get('description', '')
|
||||
if not link_name:
|
||||
if 'button' in attachment[atype] and 'action' in attachment[atype]['button']:
|
||||
link_name = attachment[atype]['button']['action'].get('title', '')
|
||||
if not link_name:
|
||||
link_name = 'Прикрепленная ссылка'
|
||||
elif len(link_name) > 200:
|
||||
link_name = link_name[:200] + '...'
|
||||
photo_content = ''
|
||||
if 'photo' in attachment[atype]:
|
||||
photo_url = attachment[atype]['photo'][await get_max_photo(attachment[atype]['photo'])]
|
||||
photo_name = attachment[atype]['photo'].get('text', '​')
|
||||
if not photo_name:
|
||||
photo_name = '​'
|
||||
photo_content = f'<a href="{photo_url}">{photo_name}</a>'
|
||||
if photo_name != '​':
|
||||
photo_content += '\n'
|
||||
return {'content': photo_content + f'<a href="{link_url}">🔗 {link_name}</a>', 'type': 'text'}
|
||||
|
||||
elif atype == 'market':
|
||||
market_url = f'https://vk.com/market{attachment[atype]["owner_id"]}_{attachment[atype]["id"]}'
|
||||
photo_content = ''
|
||||
if attachment[atype].get('thumb_photo'):
|
||||
photo_content = f'<a href="{attachment[atype]["thumb_photo"]}">​</a>'
|
||||
title = f'<a href="{market_url}">{attachment[atype].get("title", "") or "🛍 Товар"}</a>'
|
||||
description = attachment[atype].get('description', '')
|
||||
if description:
|
||||
description = f'\n<i>{description}</i>'
|
||||
price = ''
|
||||
if attachment[atype].get('price'):
|
||||
price = f'\n<b>{attachment[atype]["price"]["text"]}</b>'
|
||||
|
||||
return {'content': photo_content + title + description + price + '\n', 'type': 'text'}
|
||||
|
||||
elif atype == 'market_album':
|
||||
market_album_url = f'https://vk.com/market{attachment[atype]["owner_id"]}?section=album_{attachment[atype]["id"]}'
|
||||
photo_content = ''
|
||||
if attachment[atype].get('photo'):
|
||||
photo_url = attachment[atype]['photo'][await get_max_photo(attachment[atype])]
|
||||
photo_content = f'<a href="{photo_url or ""}">​</a>'
|
||||
title = f'<a href="{market_album_url}">{attachment[atype].get("title", "") or "🛒 Подборка Товаров"}</a>'
|
||||
count = f'\n<i>Число товаров:</i> {attachment[atype]["count"]}'
|
||||
return {'content': photo_content + title + count + '\n', 'type': 'text'}
|
||||
|
||||
elif atype == 'wall':
|
||||
owner_id = attachment[atype].get('owner_id', '') or attachment[atype].get('from_id', '') or attachment[
|
||||
atype].get('to_id', '')
|
||||
post_id = attachment[atype]['id']
|
||||
# access_key = attachment[atype].get('access_key')
|
||||
wall_url = f'https://vk.com/wall{owner_id}_{post_id}' # + f'_{access_key}' if access_key else ''
|
||||
return {'content': f'<a href="{wall_url}">📰 Запись на стене</a>', 'type': 'text'}
|
||||
|
||||
elif atype == 'wall_reply':
|
||||
owner_id = attachment[atype].get('owner_id', '') or attachment[atype].get('from_id', '') or attachment[
|
||||
atype].get('to_id', '')
|
||||
post_id = attachment[atype]['post_id']
|
||||
wall_reply_url = f'https://vk.com/wall{owner_id}_{post_id}'
|
||||
reply_text = attachment[atype].get('text', '')
|
||||
if reply_text:
|
||||
reply_text = '\n' + reply_text
|
||||
return {'content': f'<a href="{wall_reply_url}">💬 Комментарий к записи</a>{reply_text}', 'type': 'text'}
|
||||
|
||||
|
||||
async def vk_polling(vkuser: VkUser):
|
||||
while True:
|
||||
try:
|
||||
session = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
session.API_VERSION = API_VERSION
|
||||
log.warning('Starting polling for: id ' + str(vkuser.pk))
|
||||
api = API(session)
|
||||
lp = LongPoll(session, mode=10, version=4)
|
||||
while VkUser.objects.filter(token=vkuser.token, is_polling=True).exists():
|
||||
data = await lp.wait()
|
||||
log.debug('Longpoll: ' + str(data))
|
||||
if data['updates']:
|
||||
for update in data['updates']:
|
||||
await process_longpoll_event(api, update)
|
||||
break
|
||||
except VkLongPollError:
|
||||
log.error('Longpoll error! {}'.format(vkuser.pk))
|
||||
asyncio.sleep(5)
|
||||
except VkAuthError:
|
||||
log.error('Auth Error! {}'.format(vkuser.pk))
|
||||
vkuser.is_polling = False
|
||||
vkuser.save()
|
||||
break
|
||||
except CancelledError:
|
||||
log.warning('Stopped polling for: id ' + str(vkuser.pk))
|
||||
break
|
||||
except Exception:
|
||||
log.exception(msg='Error in longpolling', exc_info=True)
|
||||
asyncio.sleep(5)
|
||||
|
||||
|
||||
def vk_polling_tasks():
|
||||
tasks = [{'token': vkuser.token, 'task': asyncio.ensure_future(vk_polling(vkuser))} for vkuser in
|
||||
VkUser.objects.filter(token__isnull=False, is_polling=True)]
|
||||
log.warning('Starting Vk polling')
|
||||
return tasks
|
Loading…
x
Reference in New Issue
Block a user