Example #1
0
class VkBot(CommonBot, Thread):
    def __init__(self):
        Thread.__init__(self)
        CommonBot.__init__(self, Platform.VK)

        self.token = env.str('VK_BOT_TOKEN')
        self.group_id = env.str('VK_BOT_GROUP_ID')
        vk_session = VkApi(token=self.token, api_version="5.107", config_filename="secrets/vk_bot_config.json")
        self.longpoll = MyVkBotLongPoll(vk_session, group_id=self.group_id)
        self.upload = VkUpload(vk_session)
        self.vk = vk_session.get_api()

        self.vk_user = VkUser()

        self.test_chat = Chat.objects.get(chat_id=env.str("VK_TEST_CHAT_ID"))

    def set_activity(self, peer_id, activity='typing'):
        if activity not in ['typing', 'audiomessage']:
            raise PWarning("Не знаю такого типа активности")
        self.vk.messages.setActivity(type=activity, peer_id=peer_id, group_id=self.group_id)

    def get_user_by_id(self, user_id):
        vk_user = self.user_model.filter(user_id=user_id)
        if len(vk_user) > 0:
            vk_user = vk_user.first()
        else:
            # Прозрачная регистрация
            user = self.vk.users.get(user_id=user_id, lang='ru', fields='sex, bdate, city, screen_name')[0]
            vk_user = Users()
            vk_user.user_id = user_id
            vk_user.name = user['first_name']
            vk_user.surname = user['last_name']
            vk_user.platform = self.platform.name

            if 'sex' in user:
                vk_user.gender = user['sex']
            if 'bdate' in user:
                vk_user.birthday = self.parse_date(user['bdate'])
            if 'city' in user:
                from apps.service.models import City
                city_name = user['city']['title']
                city = City.objects.filter(name=city_name)
                if len(city) > 0:
                    city = city.first()
                else:
                    try:
                        city = add_city_to_db(city_name)
                    except Exception:
                        city = None
                vk_user.city = city
            else:
                vk_user.city = None
            if 'screen_name' in user:
                vk_user.nickname = user['screen_name']
            vk_user.save()
            group_user = Group.objects.get(name=Role.USER.name)
            vk_user.groups.add(group_user)
            vk_user.save()
        return vk_user

    def get_chat_by_id(self, chat_id):
        vk_chat = self.chat_model.filter(chat_id=chat_id)
        if len(vk_chat) > 0:
            vk_chat = vk_chat.first()
        else:
            vk_chat = Chat(chat_id=chat_id, platform=self.platform.name)
            vk_chat.save()
        return vk_chat

    @staticmethod
    def add_extra_group_to_user(user, chat):
        group = ScheduleGroup.objects.filter(conference=chat).first()
        if group:
            groups = user.groups
            group_student = Group.objects.get(name=Role.STUDENT.name)
            if group_student not in groups.all():
                user.groups.add(group_student)
                user.save()

    def get_bot_by_id(self, bot_id):
        if bot_id > 0:
            bot_id = -bot_id
        bot = self.bot_model.filter(bot_id=bot_id)
        if len(bot) > 0:
            bot = bot.first()
        else:
            # Прозрачная регистрация
            vk_bot = self.vk.groups.getById(group_id=bot_id)[0]

            bot = Bot()
            bot.bot_id = bot_id
            bot.name = vk_bot['name']
            bot.platform = self.platform.name
            bot.save()

        return bot

    def send_message(self, peer_id, msg="ᅠ", attachments=None, keyboard=None, dont_parse_links=False, **kwargs):
        if attachments is None:
            attachments = []
        if isinstance(attachments, str):
            attachments = [attachments]
        if attachments and msg == "ᅠ":
            msg = ""
        if keyboard:
            keyboard = json.dumps(keyboard)
        msg = str(msg)
        if len(msg) > 4096:
            msg = msg[:4092]
            msg += "\n..."
        try:
            self.vk.messages.send(peer_id=peer_id,
                                  message=msg,
                                  access_token=self.token,
                                  random_id=get_random_id(),
                                  attachment=','.join(attachments),
                                  keyboard=keyboard,
                                  dont_parse_links=dont_parse_links
                                  )
        except vk_api.exceptions.ApiError as e:
            if e.code == 901:
                pass
            else:
                print("Ошибка отправки сообщения\n"
                      f"{e}")

    def _setup_event(self, event):
        vk_event = {
            'platform': self.platform,
            'from_user': event.from_user,
            'chat_id': event.chat_id,
            'user_id': event.message.from_id,
            'peer_id': event.message.peer_id,
            'message': {
                # 'id': event.message.id,
                'text': event.message.text,
                'payload': event.message.payload,
                'attachments': event.message.attachments,
                'action': event.message.action
            },
            'fwd': None
        }
        if (vk_event['message'].get('action', None)
                and vk_event['message']['action']['type'] in ['chat_invite_user', 'chat_invite_user_by_link']):
            vk_event['message']['action']['member_ids'] = [vk_event['message']['action'].pop('member_id')]
        return vk_event

    def listen(self):
        for event in self.longpoll.listen():
            try:
                # Если пришло новое сообщение
                if event.type == VkBotEventType.MESSAGE_NEW:
                    vk_event = self._setup_event(event)

                    if self.DEVELOP_DEBUG:
                        from_test_chat = vk_event['chat_id'] == self.test_chat.id
                        from_me = str(vk_event['user_id']) == env.str('VK_ADMIN_ID')
                        if not from_test_chat or not from_me:
                            continue

                    # Сообщение либо мне в лс, либо упоминание меня, либо есть аудиосообщение, либо есть экшн
                    if not self.need_a_response(vk_event):
                        continue

                    # Обработка вложенных сообщений в event['fwd']. reply и fwd для вк это разные вещи.
                    if event.message.reply_message:
                        vk_event['fwd'] = [event.message.reply_message]
                    elif len(event.message.fwd_messages) != 0:
                        vk_event['fwd'] = event.message.fwd_messages

                    # Узнаём пользователя
                    if vk_event['user_id'] > 0:
                        vk_event['sender'] = self.get_user_by_id(vk_event['user_id'])
                    else:
                        self.send_message(vk_event['peer_id'], "Боты не могут общаться с Петровичем :(")
                        continue

                    # Узнаём конфу
                    if vk_event['chat_id']:
                        vk_event['chat'] = self.get_chat_by_id(int(vk_event['peer_id']))
                        if vk_event['sender'] and vk_event['chat']:
                            self.add_chat_to_user(vk_event['sender'], vk_event['chat'])
                            self.add_extra_group_to_user(vk_event['sender'], vk_event['chat'])
                    else:
                        vk_event['chat'] = None

                    vk_event_object = VkEvent(vk_event)
                    thread = threading.Thread(target=self.menu, args=(vk_event_object,))
                    thread.start()
            except Exception as e:
                print(str(e))
                tb = traceback.format_exc()
                print(tb)

    @staticmethod
    def _prepare_obj_to_upload(file_like_object, allowed_exts_url=None):
        # bytes array
        if isinstance(file_like_object, bytes):
            obj = io.BytesIO(file_like_object)
            obj.seek(0)
        # bytesIO
        elif isinstance(file_like_object, io.BytesIO):
            obj = file_like_object
            obj.seek(0)
        # url
        elif urlparse(file_like_object).hostname:
            if allowed_exts_url:
                extension = file_like_object.split('.')[-1].lower()
                is_default_extension = extension not in allowed_exts_url
                is_vk_image = any(extension.startswith(x) for x in allowed_exts_url)
                if is_default_extension and not is_vk_image:
                    raise PWarning(f"Загрузка по URL доступна только для {' '.join(allowed_exts_url)}")
            try:
                response = requests.get(file_like_object, stream=True, timeout=3)
            except SSLError:
                raise PWarning("SSLError")
            except requests.exceptions.ConnectionError:
                raise PWarning("ConnectionError")
            obj = response.raw
        # path
        else:
            obj = file_like_object
        return obj

    def _get_attachment_by_id(self, _type, group_id, _id):
        if group_id is None:
            group_id = f'-{self.group_id}'
        return f"{_type}{group_id}_{_id}"

    def upload_photos(self, images, max_count=10):
        if not isinstance(images, list):
            images = [images]

        attachments = []
        images_to_load = []
        for image in images:
            try:
                image = self._prepare_obj_to_upload(image, ['jpg', 'jpeg', 'png'])
            except PWarning:
                continue
            except ReadTimeout:
                continue
            except ConnectTimeout:
                continue
            # Если Content-Length > 50mb
            bytes_count = None
            if isinstance(image, io.BytesIO):
                bytes_count = image.getbuffer().nbytes
            elif isinstance(image, urllib3.response.HTTPResponse) or isinstance(image,
                                                                                requests.packages.urllib3.response.HTTPResponse):
                bytes_count = image.headers.get('Content-Length')
            elif os.path.exists(image):
                bytes_count = os.path.getsize(image)
            else:
                print("ШТО ТЫ ТАКОЕ", type(image))
            if not bytes_count:
                continue
            if int(bytes_count) / 1024 / 1024 > 50:
                continue
            images_to_load.append(image)

            if len(images_to_load) >= max_count:
                break
        try:
            vk_photos = self.upload.photo_messages(images_to_load)
            for vk_photo in vk_photos:
                attachments.append(self._get_attachment_by_id('photo', vk_photo['owner_id'], vk_photo['id']))
        except vk_api.exceptions.ApiError as e:
            print(e)
        return attachments

    def upload_document(self, document, peer_id=None, title='Документ'):
        document = self._prepare_obj_to_upload(document)
        vk_document = self.upload.document_message(document, title=title, peer_id=peer_id)['doc']
        return self._get_attachment_by_id('doc', vk_document['owner_id'], vk_document['id'])

    def upload_audio(self, audio, artist, title):
        audio = self._prepare_obj_to_upload(audio)
        try:
            vk_audio = self.vk_user.upload.audio(audio, artist=artist, title=title)
        except vk_api.exceptions.ApiError as e:
            if e.code == 270:
                raise PWarning("Аудиозапись была удалена по просьбе правообладателя")
            raise PError("Ошибка загрузки аудиозаписи")
        return self.get_attachment_by_id('audio', vk_audio['owner_id'], vk_audio['id'])

    @staticmethod
    def get_inline_keyboard(command_text, button_text="Ещё", args=None):
        if args is None:
            args = {}
        return {
            'inline': True,
            'buttons': [[
                {
                    'action': {
                        'type': 'text',
                        'label': button_text,
                        "payload": json.dumps({"command": command_text, "args": args}, ensure_ascii=False)
                    },
                    'color': 'primary',
                }
            ]]}

    @staticmethod
    def get_group_id(_id):
        return 2000000000 + int(_id)

    @staticmethod
    def get_mention(user, name=None):
        name = name or str(user)
        return f"[id{user.user_id}|{name}]"

    def upload_video_by_link(self, link, name):
        values = {
            'name': name,
            'is_private': False,
            'link': link,
        }

        response = self.vk_user.vk.video.save(**values)
        requests.post(response['upload_url'])
        return f"video{response['owner_id']}_{response['video_id']}"

    def get_attachment_by_id(self, _type, group_id, _id):
        if group_id is None:
            group_id = f'-{self.group_id}'
        return f"{_type}{group_id}_{_id}"

    def get_video(self, owner_id, _id):
        return self.vk_user.vk.video.get(videos=f'{owner_id}_{_id}')

    def set_chat_title(self, chat_id, title):
        self.vk.messages.editChat(chat_id=chat_id, title=title)

    def set_chat_title_if_not_equals(self, chat_id, title):
        if title != self.vk.messages.getConversationsById(peer_ids=chat_id)['items'][0]['chat_settings']['title']:
            self.set_chat_title(int(chat_id) - 2000000000, title)
            return True
        return False

    def get_conference_users(self, peer_id):
        try:
            return self.vk.messages.getConversationMembers(peer_id=peer_id, group_id=self.group_id, lang='ru')[
                'profiles']
        except:
            raise PWarning("У бота нет админских прав для получения списка пользователей в конференции")
Example #2
0
class VkBot(CommonBot):
    def __init__(self):
        CommonBot.__init__(self, Platform.VK)

        self.token = env.str('VK_BOT_TOKEN')
        self.group_id = env.str('VK_BOT_GROUP_ID')
        vk_session = VkApi(token=self.token,
                           api_version="5.131",
                           config_filename="secrets/vk_bot_config.json")
        self.longpoll = MyVkBotLongPoll(vk_session, group_id=self.group_id)
        self.upload = VkUpload(vk_session)
        self.vk = vk_session.get_api()

    # MAIN ROUTING AND MESSAGING

    def listen(self):
        """
        Получение новых событий и их обработка
        """
        for raw_event in self.longpoll.listen():
            vk_event = VkEvent(raw_event, self)
            threading.Thread(target=self.handle_event,
                             args=(vk_event, )).start()

    def send_response_message(self, rm: ResponseMessage):
        """
        Отправка ResponseMessage сообщения
        Вовзращает список результатов отправки в формате
        [{success:bool, response:Response, response_message_item:ResponseMessageItem}]
        """
        results = []
        for rmi in rm.messages:
            try:
                response = self.send_response_message_item(rmi)
                results.append({
                    "success": True,
                    "response": response,
                    "response_message_item": rmi
                })
            except vk_api.exceptions.ApiError as e:
                if e.code not in [901, 917]:
                    error_rm = ResponseMessage(self.ERROR_MSG,
                                               rmi.peer_id).messages[0]
                    self.logger.error({
                        'message': self.ERROR_MSG,
                        'error': str(e)
                    })
                    response = self.send_response_message_item(error_rm)
                    results.append({
                        "success": False,
                        "response": response,
                        "response_message_item": error_rm
                    })
        return results

    def send_response_message_item(self, rm: ResponseMessageItem):
        """
        Отправка ResponseMessageItem сообщения
        Возвращает Response платформы
        """
        text = str(rm.text)
        if len(text) > 4096:
            text = text[:4092]
            text += "\n..."

        attachments = []
        for att in rm.attachments:
            if isinstance(att, str):
                attachments.append(att)
            elif att.url:
                attachments.append(att.url)
            elif att.public_download_url:
                attachments.append(att.public_download_url)

        return self.vk.messages.send(
            peer_id=rm.peer_id,
            message=text,
            access_token=self.token,
            random_id=get_random_id(),
            attachment=','.join(attachments),
            keyboard=json.dumps(rm.keyboard),
        )

    # END MAIN ROUTING AND MESSAGING

    # ATTACHMENTS

    def upload_photos(self, images, max_count=10, peer_id=None):
        """
        Загрузка фотографий на сервер ТГ.
        images: список изображений в любом формате (ссылки, байты, файлы)
        При невозможности загрузки одной из картинки просто пропускает её
        """
        atts = super().upload_photos(images, max_count)
        parsed_atts = []
        for pa in atts:
            try:
                url, public_download_url = self.upload_photo_and_urls(pa)
                pa.url = url.replace(VK_URL, '')
                pa.public_download_url = public_download_url
                parsed_atts.append(pa)
            except Exception:
                continue
        return parsed_atts

    def upload_photo_and_urls(self, image: PhotoAttachment):
        """
        Загрузка изображения на сервер VK
        Возвращает vk_url и public_download_url
        """
        vk_photo = self.upload.photo_messages(image.get_bytes_io_content())[0]
        vk_url = f"{VK_URL}photo{vk_photo['owner_id']}_{vk_photo['id']}"
        vk_max_photo_url = sorted(vk_photo['sizes'],
                                  key=lambda x: x['height'])[-1]['url']
        return vk_url, vk_max_photo_url

    def upload_document(self,
                        document,
                        peer_id=None,
                        title='Документ',
                        filename=None):
        """
        Загрузка документа на сервер ВК.
        """
        da = super().upload_document(document, peer_id, title, filename)
        content = da.download_content()
        vk_doc = self.upload.document_message(content,
                                              title=title,
                                              peer_id=peer_id)['doc']
        return f"doc{vk_doc['owner_id']}_{vk_doc['id']}"

    # END ATTACHMENTS

    # USERS GROUPS BOTS

    def get_profile_by_user(self,
                            user: User,
                            is_new=False,
                            _defaults: dict = None) -> Profile:
        """
        Возвращает пользователя по его id
        Регистрирует если пользователя нет в БД
        """
        if not user.profile:
            vk_user = self.get_user_info(int(user.user_id))
            profile = Profile()
            profile.name = vk_user['first_name']
            profile.surname = vk_user['last_name']
            profile.platform = self.platform.name
            profile.set_avatar(vk_user['photo_max'])

            if 'sex' in vk_user:
                profile.gender = vk_user['sex']
            if 'city' in vk_user:
                from apps.service.models import City
                city_name = vk_user['city']['title']
                city = City.objects.filter(name__icontains=city_name)
                if len(city) > 0:
                    city = city.first()
                else:
                    try:
                        city = add_city_to_db(city_name)
                    except Exception:
                        city = None
                profile.city = city
            else:
                profile.city = None
            if 'screen_name' in vk_user:
                user.nickname = vk_user['screen_name']
            with self.lock:
                profile.save()
                user.profile = profile
                user.save()
            return super().get_profile_by_user(user, is_new=True)
        return super().get_profile_by_user(user)

    def get_user_info(self, user_id: int):
        """
        Получение информации о пользователе
        """
        return self.vk.users.get(
            user_id=user_id,
            lang='ru',
            fields='sex, bdate, city, screen_name, photo_max')[0]

    def update_profile_avatar(self, profile: Profile, user_id):
        """
        Обновление аватара пользователя
        """
        user_info = self.get_user_info(user_id)
        profile.set_avatar(user_info['photo_max'])

    def get_bot_by_id(self, bot_id: int) -> BotModel:
        """
        Получение информации о боте
        """
        try:
            bot = self.bot_model.get(bot_id=bot_id)
        except BotModel.DoesNotExist:
            bot = super().get_bot_by_id(bot_id)
            vk_bot = self.get_bot_info(bot_id)
            bot.name = vk_bot['name']
            bot.set_avatar(vk_bot['photo_200'])
            bot.save()
        return bot

    def get_bot_info(self, bot_id):
        """
        Получение информации о боте
        """
        return self.vk.groups.getById(group_id=bot_id)[0]

    def update_bot_avatar(self, bot_id):
        """
        Обновление аватара бота
        """
        bot = self.get_bot_by_id(bot_id)
        bot_info = self.get_bot_info(bot_id)
        bot.name = bot_info['name']
        bot.set_avatar(bot_info['photo_200'])

    # END USERS GROUPS BOTS

    # EXTRA

    def set_activity(self, peer_id, activity: ActivitiesEnum):
        """
        Метод позволяет указать пользователю, что бот набирает сообщение или записывает голосовое
        Используется при длительном выполнении команд, чтобы был фидбек пользователю, что его запрос принят
        """
        tg_activity = VK_ACTIVITIES.get(activity)
        if tg_activity:
            self.vk.messages.setActivity(type=tg_activity,
                                         peer_id=peer_id,
                                         group_id=self.group_id)

    @staticmethod
    def _get_keyboard_buttons(buttons):
        """
        Определение структуры кнопок
        """

        return [{
            'action': {
                'type':
                'text',
                'label':
                button_item['button_text'],
                "payload":
                json.dumps(
                    {
                        "command": button_item['command'],
                        "args": button_item.get('args'),
                    },
                    ensure_ascii=False)
            },
            'color': 'primary',
        } for button_item in buttons]

    def get_inline_keyboard(self, buttons: list, cols=1):
        """
        param buttons: ToDo:
        Получение инлайн-клавиатуры с кнопками
        В основном используется для команд, где нужно запускать много команд и лень набирать заново
        """
        keyboard = super().get_inline_keyboard(buttons)
        return {'inline': True, 'buttons': keyboard}

    def get_mention(self, profile: Profile, name=None):
        """
        Получение меншона пользователя
        """
        user = profile.get_user_by_platform(self.platform)
        name = name or str(user)
        return f"[id{user.user_id}|{name}]"

    def remove_self_from_chat(self, chat_id):
        """
        Удаление бота (себя) из чата
        """
        self.vk.messages.removeChatUser(chat_id=chat_id,
                                        member_id=f"-{self.group_id}")

    def get_conversation_messages(self, peer_id, conversation_message_id):
        """
        Получение полного сообщения
        """
        response = self.vk.messages.getByConversationMessageId(
            peer_id=peer_id,
            conversation_message_ids=[conversation_message_id])
        return response['items'][0]
Example #3
0
def main():
    """
    работа чат-бота по запросам от пользователя
    :return: сообщения чат-бота с прогнозом из БД по случайному числу, выданное в личном чате сообщества, или ответы на запросы
    """
    load_dotenv()  # .env
    vk_session = vk_api.VkApi(token=env['VK_TOKEN'])  # токен
    longpoll = VkBotLongPoll(vk_session, int(env['VK_BOT_ID']))  # ID
    vk = vk_session.get_api()  # открытие сессии
    m = -1  # параметр для отслеживания выдачи ответов без бросания монет
    # запуск сессии чат-бота
    for event in longpoll.listen():
        if event.type == VkBotEventType.MESSAGE_NEW:
            from_id = event.obj.message['from_id']
            text = event.obj.message['text'].lower()
            print(f'Новое сообщение {text} от {from_id}'
                  )  # оповещение о новом сообщении от пользователя
            # обработка сообщений пользователя
            # запрос на получение ответа
            if text in ['получить ответ', 'ответ', 'answer']:
                if m == -1:
                    vk.messages.send(
                        user_id=from_id,
                        keyboard=json.dumps(keyboards.keyboard[5],
                                            ensure_ascii=False),
                        message=
                        f'Вы хотите получить ответ без бросания монет? Так ничего не получится. Нужно строго соблюдать ритуал! \n \n'
                        f'Пожалуйста, наберите "Бросить" или "бросить" или нажмите на кнопку "Бросить монеты"',
                        random_id=random.randint(0, 2**64))
                else:
                    # получение по числу m предсказания из БД
                    mess = work_in_db(m)
                    # печать картинки гексаграммы из БД
                    attachments = []
                    from vk_api import VkUpload
                    upload = VkUpload(vk_session)
                    image = f'data/Img/{m}.png'
                    photo = upload.photo_messages(photos=image)[0]
                    attachments.append('photo{}_{}'.format(
                        photo['owner_id'], photo['id']))
                    vk.messages.send(user_id=from_id,
                                     attachment=','.join(attachments),
                                     random_id=random.randint(0, 2**64))
                    # печать текста предсказания и выдача клавиатуры для дальнейших действий
                    vk.messages.send(
                        user_id=from_id,
                        keyboard=json.dumps(keyboards.keyboard[1],
                                            ensure_ascii=False),
                        message=
                        f'Вам выпала гексаграмма {chr(mess[0][2])} {mess[0][0]} - {mess[0][1]} \n \n Ее значение: \n {mess[1]} \n \n'
                        f'Если Вы хотите сохранить предсказание, то напишите "сохранить" или "Сохранить", или нажмите '
                        f'кнопку "Сохранить предсказание"',
                        random_id=random.randint(0, 2**64))
                    m = -1
            # запрос на сохранение ответа
            elif text in ['сохранить предсказание', 'сохранить', 'save']:
                f = open('prognoz.txt', 'w')
                st = ''.join(
                    f'Вам выпала гексаграмма {mess[0][0]} - {mess[0][1]} \n \n Ее значение: \n {mess[1]}'
                )
                f.write(st)
                f.close()
                # информация для пользователя о сохранении предсказания и выдача клавиатуры для дальнейших действий
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[2],
                                        ensure_ascii=False),
                    message=f'Предсказание сохранено. \n \n'
                    f'Если Вы хотите скачать сохраненное предсказание, то напишите "Скачать" или "скачать", или '
                    f'нажмите кнопку "Скачать файл с предсказанием"',
                    random_id=random.randint(0, 2**64))
            # запрос на скачивание файла с ответом
            elif text in [
                    'скачать файл с предсказанием', 'скачать', 'download'
            ]:
                attachments = []
                from vk_api import VkUpload
                upload = VkUpload(vk_session)
                pr = f'data/Prognoz/{mess[0][1]}.txt'
                document = upload.document_message(doc=pr,
                                                   title='Ваше предсказание',
                                                   peer_id=from_id)
                print(document)
                attachments.append('doc{}_{}'.format(
                    document['doc']['owner_id'], document['doc']['id']))
                # выдача файла с предсказанием пользователю
                vk.messages.send(keyboard=json.dumps(keyboards.keyboard[0],
                                                     ensure_ascii=False),
                                 user_id=from_id,
                                 attachment=','.join(attachments),
                                 random_id=random.randint(0, 2**64))
            # запрос, как задать вопрос или для задания нового вопроса
            elif text in [
                    'вопрос', 'как задать свой вопрос?', 'задать новый вопрос',
                    'question'
            ]:
                # описание действия при формулировке вопроса и выдача клавиатуры для дальнейших действий
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[4],
                                        ensure_ascii=False),
                    message=
                    f'В гадании принято бросать шесть монет для формирования гексаграммы ответа. \n \n'
                    f'Монеты падают случайным образом, каждая из них будет лежать одной стороной вверх -'
                    f' орлом или решкой, это и сформирует нужный для предсказания код. \n \n'
                    f'Если Вы первый раз зашли на ресурс и не знакомы с монетами И-Цзин, нажав на кнопку '
                    f'"Монеты", Вы получите информацию о сторонах монеты И-Цзин. \n \n'
                    f'Хорошо продумайте свой вопрос, задайте его Высшим силам и нажмите кнопку "Бросить монеты".',
                    random_id=random.randint(0, 2**64))
            # запрос на бросание монет
            elif text in ['бросить монеты', 'бросить', 'toss coins']:
                # получение случайного числа от 0 до 63
                m = random.randint(0, 63)
                # формирование его двоичного кода
                mm = bin(m)[2:]
                # дополнение двоичного кода до 6 знаков не значащими нулями
                if len(mm) < 6:
                    mm = (6 - len(mm)) * '0' + mm
                # разбор кода и составление набора из 6 монет - орлы и решки
                attachments = []
                for x in mm:
                    if x == '0':
                        from vk_api import VkUpload
                        upload = VkUpload(vk_session)
                        image = f'data/Img/moneta_reshka.png'
                        photo = upload.photo_messages(photos=image)[0]
                        attachments.append('photo{}_{}'.format(
                            photo['owner_id'], photo['id']))
                    else:
                        from vk_api import VkUpload
                        upload = VkUpload(vk_session)
                        image = f'data/Img/moneta_orel.png'
                        photo = upload.photo_messages(photos=image)[0]
                        attachments.append('photo{}_{}'.format(
                            photo['owner_id'], photo['id']))
                # печать картинки конфигурации монет
                vk.messages.send(user_id=from_id,
                                 attachment=','.join(attachments),
                                 random_id=random.randint(0, 2**64))
                # пояснения к расшифровке картинки и выдача клавиатуры для дальнейших действий
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[3],
                                        ensure_ascii=False),
                    message=
                    f'Гексаграмма записывается снизу вверх, т.е. первая монета определяет нижнюю черту и так далее. \n'
                    f'Сплошная линия соответствует орлу, прерванная линия - решке. \n \n'
                    f'Нажмите кнопку "Получить ответ" или наберите текст "ответ" или "Ответ". \n \n'
                    f'Вы получите предсказание по Вашему вопросу согласно комбинации выпавших монет.',
                    random_id=random.randint(0, 2**64))
            # запрос на получение команд бота
            elif text in ['правила', 'rules', 'commands']:
                # правила общения в сообществе
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[0],
                                        ensure_ascii=False),
                    message=f'Правила для управления ботом: \n \n'
                    f'Для демонстрации клавиатуры нужно набрать сообщение с текстом "Клавиатура" или "клавиатура".\n \n'
                    f'Для повторного ознакомления с правилами наберите "правила" или "Правила", или нажмите кнопку "Правила" \n \n'
                    f'Для того, чтобы получить указания, что делать дальше, наберите "вопрос" или "Вопрос", или '
                    f'нажмите кнопку "Как задать свой вопрос?"\n \n'
                    f'Далее - следуйте инструкциям бота.\n \n'
                    f'Удачи Вам и исполнения желаний!',
                    random_id=random.randint(0, 2**64))
            # запрос на получение стартовой клавиатуры - кнопок бота
            elif text in ['клавиатура', 'keyboard']:
                # настройка стартовой клавиатуры для пользователя
                vk.messages.send(user_id=from_id,
                                 keyboard=json.dumps(keyboards.keyboard[0],
                                                     ensure_ascii=False),
                                 key=(env['VK_TOKEN']),
                                 random_id=random.randint(0, 2**64),
                                 message='Держи!')
            # запрос на получение информации о монетах И-Цзин
            elif text in ['coins', 'монеты', 'coin', 'монета']:
                # информация об орле и решке монет И-Цзин
                from vk_api import VkUpload
                upload = VkUpload(vk_session)
                image = f'data/Img/moneta_orel.png'
                photo = upload.photo_messages(photos=image)[0]
                attachments = []
                attachments.append('photo{}_{}'.format(photo['owner_id'],
                                                       photo['id']))
                vk.messages.send(user_id=from_id,
                                 attachment=','.join(attachments),
                                 message=f'Лицевая сторона монеты - орел',
                                 random_id=random.randint(0, 2**64))
                attachments = []
                image = f'data/Img/moneta_reshka.png'
                photo = upload.photo_messages(photos=image)[0]
                attachments.append('photo{}_{}'.format(photo['owner_id'],
                                                       photo['id']))
                vk.messages.send(user_id=from_id,
                                 keyboard=json.dumps(keyboards.keyboard[5],
                                                     ensure_ascii=False),
                                 attachment=','.join(attachments),
                                 message=f'Тыльная сторона монеты - решка.',
                                 random_id=random.randint(0, 2**64))
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[5],
                                        ensure_ascii=False),
                    message=
                    f'Хорошо продумайте свой вопрос, задайте его Высшим силам и нажмите кнопку "Бросить монеты".',
                    random_id=random.randint(0, 2**64))
            # вступительный текст с инструкцией для нерегламентированных сообщений от пользователя
            else:
                vk.messages.send(
                    user_id=from_id,
                    keyboard=json.dumps(keyboards.keyboard[0],
                                        ensure_ascii=False),
                    key=(env['VK_TOKEN']),
                    message=
                    f'Если Вы в первый раз пользуетесь нашим ресурсом, то нужно ознакомиться с Правилами сообщества. \n \n'
                    f'Для этого введите "правила" или "Правила" в строку сообщения, или нажмите кнопку "Правила" \n \n'
                    f'Для тех, кто уже знаком с правилами, - введите "вопрос" или "Вопрос", или нажмите кнопку "Как задать свой вопрос?',
                    random_id=random.randint(0, 2**64))
        # обработка событий, когда долго нет пользователей - для сохранения онлайн сессии от прерываний со стороны ВК
        elif event.type == VkBotEventType.MESSAGE_EVENT:
            event_id = event.obj.message['event_id']
            vk.messages.send(user_id=from_id,
                             event_id=event_id,
                             keyboard=json.dumps(keyboards.keyboard[0],
                                                 ensure_ascii=False),
                             message=f'Жду вопросов...',
                             random_id=random.randint(0, 2**64))
Example #4
0
def main():
    vk = vk_session.get_api()
    longpoll = VkLongPoll(vk_session)
    upload = VkUpload(vk_session)

    print("Started")

    for event in longpoll.listen():
        if event.type == VkEventType.MESSAGE_NEW and event.to_me and event.text:
            pprint(event.__dict__)
            print('{}: id{}: "{}"'.format(event.message_id, event.user_id,
                                          event.text))

            if (event.text == ''):
                vk.messages.send(
                    user_id=event.user_id,
                    random_id=get_random_id(),
                    message=
                    'Message text is required. Please enter a name or archive and resend your message.'
                )
                continue

            messages_data = vk_session.method(
                'messages.getById', {'message_ids': set([
                    event.message_id,
                ])})
            pprint(messages_data)
            messages_data = messages_data['items'][0]

            links = get_photos_links(messages_data)
            print(links)
            print("len = ", len(links))

            if (len(links) == 0):
                vk.messages.send(user_id=event.user_id,
                                 random_id=get_random_id(),
                                 message='No photos')
                continue
            muid = str(event.user_id) + str(event.message_id)
            pics_path = os.path.join('data', muid)
            if not os.path.exists(pics_path):
                os.makedirs(pics_path)

            for c, plink in enumerate(links):
                print(c)
                with urllib.request.urlopen(plink) as response, open(
                        os.path.join('data', muid,
                                     str(c) + '.jpg'), 'wb') as out_file:
                    data = response.read()  # a `bytes` object
                    out_file.write(data)

            zip_name = os.path.join('data', str(muid) + ".zip")
            zipf = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED)
            zipdir(pics_path, zipf)
            zipf.close()

            doc = upload.document_message(doc=zip_name,
                                          title=event.text + str(muid) +
                                          ".zip",
                                          peer_id=event.peer_id)
            doc = doc['doc']
            pprint(doc)
            vk.messages.send(user_id=event.user_id,
                             attachment='doc{}_{}'.format(
                                 doc['owner_id'], doc['id']),
                             random_id=get_random_id(),
                             message='Ok. Done.')

            print('ok')
class VKPayloadProcessor:
    def __init__(self, data: dict, vk, peer_id: int):
        self.message = ''
        self.attachments = []
        self.keyboard = None
        self.session = requests.Session()
        self.peer_id = peer_id if peer_id else None

        self.upload = VkUpload(vk)

        if data.get('text'):
            self.message = data['text']
        if data.get('image_url'):
            self.get_image_from_url(data['image_url'])
        if data.get('keyboard'):
            self.parse_keyboard(data['keyboard'])
        if data.get('attachments'):
            self.get_attachments_from_urls(data['attachments'])

    def get_image_from_url(self, url: str):
        f = self.session.get(url, stream=True)
        photo = self.upload.photo_messages(photos=f.raw,
                                           peer_id=self.peer_id)[0]
        self.attachments.append(f"photo{photo['owner_id']}_{photo['id']}")

    def get_attachments_from_urls(self, urls: List[dict]):
        for file in urls:

            if file.get('vk_id') is not None:
                self.attachments.append(file.get('vk_id'))
                return

            f = self.session.get(file.get('url'), stream=True)

            # TODO really gotta find a way around writing the file to the disk to save it
            with open('/tmp/metadata.' + file.get('extension'), 'wb') as fp:
                for chunk in f.iter_content(chunk_size=8192):
                    fp.write(chunk)

            doc = self.upload.document_message(doc='/tmp/metadata.pdf',
                                               title=file.get('title'),
                                               peer_id=self.peer_id)['doc']
            self.attachments.append(f"doc{doc['owner_id']}_{doc['id']}")

    def parse_keyboard(self, data: Dict):
        self.keyboard = VkKeyboard()
        self.keyboard.one_time = data.get('one_time') or True
        self.keyboard.inline = data.get('inline') or True

        for btn in data.get('buttons'):
            btn = btn[0]
            action_type = btn.get('action').get('type')
            if action_type == 'open_link':
                self.keyboard.add_openlink_button(
                    link=btn.get('action').get('link'),
                    label=btn.get('action').get('label'),
                    payload=btn.get('action').get('payload'),
                )
            elif action_type == 'text':
                self.keyboard.add_button(label=btn.get('action').get('label'),
                                         color=btn.get('color'))
            else:
                raise NotImplementedError('Unsupported keyboard action type!')

    def get_json_data(self) -> Dict:
        values = {
            'attachment':
            ','.join(self.attachments) if self.attachments else None,
            'message': self.message,
            'keyboard': self.keyboard.get_keyboard() if self.keyboard else None
        }
        return values
Example #6
0
class VkBotClass(threading.Thread):
    def __init__(self):
        super().__init__()
        self._TOKEN = secrets['vk']['bot']['TOKEN']
        self.group_id = secrets['vk']['bot']['group_id']
        vk_session = vk_api.VkApi(token=self._TOKEN,
                                  api_version="5.107",
                                  config_filename="secrets/vk_bot_config.json")
        self.longpoll = MyVkBotLongPoll(vk_session, group_id=self.group_id)
        self.upload = VkUpload(vk_session)
        self.vk = vk_session.get_api()
        self.mentions = secrets['vk']['bot']['mentions']

        self.vk_user = VkUserClass()

        self.BOT_CAN_WORK = True
        self.DEBUG = False
        self.DEVELOP_DEBUG = False

    @staticmethod
    def parse_date(date):
        date_arr = date.split('.')
        if len(date_arr) == 2:
            return f"1970-{date_arr[1]}-{date_arr[0]}"
        else:
            return f"{date_arr[2]}-{date_arr[1]}-{date_arr[0]}"

    def need_a_response(self, vk_event, _have_audio_message, have_action):
        message = vk_event['message']['text']
        from_user = vk_event['from_user']

        if _have_audio_message:
            return True
        if have_action:
            return True
        if len(message) == 0:
            return False
        if from_user:
            return True
        if message[0] == '/':
            return True
        for mention in self.mentions:
            if message.find(mention) != -1:
                return True
        return False

    def set_activity(self, peer_id, activity='typing'):
        if activity not in ['typing', 'audiomessage']:
            raise RuntimeWarning("Не знаю такого типа активности")
        self.vk.messages.setActivity(type=activity,
                                     peer_id=peer_id,
                                     group_id=self.group_id)

    def send_message(self,
                     peer_id,
                     msg="ᅠ",
                     attachments=None,
                     keyboard=None,
                     dont_parse_links=False,
                     **kwargs):
        if attachments is None:
            attachments = []
        if isinstance(attachments, str):
            attachments = [attachments]
        if attachments and msg == "ᅠ":
            msg = ""
        if keyboard:
            keyboard = json.dumps(keyboard)
        msg = str(msg)
        if len(msg) > 4096:
            msg = msg[:4092]
            msg += "\n..."
        try:
            self.vk.messages.send(peer_id=peer_id,
                                  message=msg,
                                  access_token=self._TOKEN,
                                  random_id=get_random_id(),
                                  attachment=','.join(attachments),
                                  keyboard=keyboard,
                                  dont_parse_links=dont_parse_links)
        except vk_api.exceptions.ApiError as e:
            if e.code == 901:
                pass
            else:
                print("Ошибка отправки сообщения\n" f"{e}")

    # Отправляет сообщения юзерам в разных потоках
    def parse_and_send_msgs_thread(self, chat_ids, message):
        if not isinstance(chat_ids, list):
            chat_ids = [chat_ids]
        for chat_id in chat_ids:
            thread = threading.Thread(target=self.parse_and_send_msgs,
                                      args=(
                                          chat_id,
                                          message,
                                      ))
            thread.start()

    def parse_and_send_msgs(self, peer_id, result):
        if isinstance(result, str) or isinstance(result, int) or isinstance(
                result, float):
            result = {'msg': result}
        if isinstance(result, dict):
            result = [result]
        if isinstance(result, list):
            for msg in result:
                if isinstance(msg, str):
                    msg = {'msg': msg}
                if isinstance(msg, dict):
                    self.send_message(peer_id, **msg)

    def menu(self, vk_event, send=True):
        from apps.API_VK.command.initial import get_commands

        # Проверяем не остановлен ли бот, если так, то проверяем вводимая команда = старт?
        if not self.check_bot_working():
            if not check_user_group(vk_event.sender, Role.ADMIN):
                return

            if vk_event.command in ['старт']:
                self.BOT_CAN_WORK = True
                # cameraHandler.resume()
                msg = "Стартуем!"
                self.send_message(vk_event.peer_id, msg)
                log_result = {'result': msg}
                logger.debug(log_result)
                return msg
            return

        group = vk_event.sender.groups.filter(name=Role.BANNED.name)
        if len(group) > 0:
            return

        if self.DEBUG and send:
            if hasattr(vk_event, 'payload') and vk_event.payload:
                debug_message = \
                    f"msg = {vk_event.msg}\n" \
                    f"command = {vk_event.command}\n" \
                    f"args = {vk_event.args}\n" \
                    f"payload = {vk_event.payload}\n"
            else:
                debug_message = \
                    f"msg = {vk_event.msg}\n" \
                    f"command = {vk_event.command}\n" \
                    f"args = {vk_event.args}\n" \
                    f"original_args = {vk_event.original_args}\n"
            self.send_message(vk_event.peer_id, debug_message)

        log_vk_event = {'vk_event': vk_event}
        logger.debug(log_vk_event)

        commands = get_commands()
        for command in commands:
            try:
                if command.accept(vk_event):
                    result = command.__class__().check_and_start(
                        self, vk_event)
                    if send:
                        self.parse_and_send_msgs(vk_event.peer_id, result)
                    append_command_to_statistics(vk_event.command)
                    log_result = {'result': result}
                    logger.debug(log_result)
                    return result
            except RuntimeWarning as e:
                msg = str(e)
                log_runtime_warning = {'result': msg}
                logger.warning(log_runtime_warning)

                if send:
                    self.parse_and_send_msgs(vk_event.peer_id, msg)
                return msg
            except RuntimeError as e:
                exception = str(e)
                log_runtime_error = {
                    'exception': exception,
                    'result': exception
                }
                logger.error(log_runtime_error)
                if send:
                    self.parse_and_send_msgs(vk_event.peer_id, exception)
                return exception
            except Exception as e:
                msg = "Непредвиденная ошибка. Сообщите разработчику в группе или команда /баг"
                tb = traceback.format_exc()
                log_exception = {'exception': str(e), 'result': msg}
                logger.error(log_exception, exc_info=tb)
                if send:
                    self.parse_and_send_msgs(vk_event.peer_id, msg)
                return msg

        if vk_event.chat and not vk_event.chat.need_reaction:
            return None
        similar_command = commands[0].names[0]
        tanimoto_max = 0
        user_groups = get_user_groups(vk_event.sender)
        for command in commands:
            # Выдача пользователю только тех команд, которые ему доступны
            command_access = command.access
            if isinstance(command_access, str):
                command_access = [command_access]
            if command_access.name not in user_groups:
                continue

            for name in command.names:
                if name:
                    tanimoto_current = tanimoto(vk_event.command, name)
                    if tanimoto_current > tanimoto_max:
                        tanimoto_max = tanimoto_current
                        similar_command = name

        msg = f"Я не понял команды \"{vk_event.command}\"\n"
        if tanimoto_max != 0:
            msg += f"Возможно вы имели в виду команду \"{similar_command}\""
        logger_result = {'result': msg}
        logger.debug(logger_result)
        if send:
            self.send_message(vk_event.peer_id, msg)
        return msg

    def listen_longpoll(self):
        for event in self.longpoll.listen():
            try:
                # Если пришло новое сообщение
                if event.type == VkBotEventType.MESSAGE_NEW:

                    # from_user - сообщение пришло из диалога с пользователем
                    # chat_id - присутствует только в чатах
                    # text - текст сообщения
                    # from_id - кто отправил сообщение (если значение отрицательное, то другой бот)
                    # user_id - id пользователя
                    # peer_id - откуда отправил сообщение (если там значение совпадает с from_id, то это from_user,
                    # если нет и значение начинается на 200000000*, то это конфа
                    # payload - скрытая информация, которая передаётся при нажатии на кнопку

                    vk_event = {
                        'from_user': event.from_user,
                        'chat_id': event.chat_id,
                        'user_id': event.message.from_id,
                        'peer_id': event.message.peer_id,
                        'message': {
                            # 'id': event.message.id,
                            'text': event.message.text,
                            'payload': event.message.payload,
                            'attachments': event.message.attachments,
                            'action': event.message.action
                        },
                        'fwd': None
                    }
                    # Если я запустился из под дебага, реагируй только на меня и только в моей конфе
                    if self.DEVELOP_DEBUG:
                        from_test_chat = vk_event['chat_id'] == TEST_CHAT_ID
                        from_me = str(
                            vk_event['user_id']) == secrets['vk']['admin_id']
                        if not from_test_chat or not from_me:
                            continue

                    # Обработка вложенных сообщений в vk_event['fwd']. reply и fwd для вк это разные вещи.
                    if event.message.reply_message:
                        vk_event['fwd'] = [event.message.reply_message]
                    elif len(event.message.fwd_messages) != 0:
                        vk_event['fwd'] = event.message.fwd_messages

                    # Проверка есть ли аудиосообщения
                    have_audio_message_flag = have_audio_message(vk_event)

                    # Проверка есть ли в сообщении action
                    have_action = vk_event['message']['action'] is not None

                    # Сообщение либо мне в лс, либо упоминание меня, либо есть аудиосообщение, либо есть экшн
                    if not self.need_a_response(
                            vk_event, have_audio_message_flag, have_action):
                        continue

                    # Узнаём пользователя
                    if vk_event['user_id'] > 0:
                        vk_event['sender'] = self.get_user_by_id(
                            vk_event['user_id'])
                    else:
                        self.send_message(
                            vk_event['peer_id'],
                            "Боты не могут общаться с Петровичем :(")
                        continue

                    # Узнаём конфу
                    if vk_event['chat_id']:
                        vk_event['chat'] = self.get_chat_by_id(
                            int(vk_event['peer_id']))
                        if vk_event['sender'] and vk_event['chat']:
                            self.add_group_to_user(vk_event['sender'],
                                                   vk_event['chat'])
                    else:
                        vk_event['chat'] = None

                    vk_event_object = VkEvent(vk_event)
                    thread = threading.Thread(target=self.menu,
                                              args=(vk_event_object, ))
                    thread.start()
                else:
                    pass

            except Exception as e:
                tb = traceback.format_exc()
                log_exception = {
                    'exception': str(e),
                }
                logger.error(log_exception, exc_info=tb)

    def run(self):
        self.listen_longpoll()

    def get_chat_title(self, chat_id):
        return self.vk.messages.getconversationById(
            peer_ids=2000000000 +
            chat_id)['items'][0]['chat_settings']['title']

    def set_chat_title(self, chat_id, title):
        self.vk.messages.editChat(chat_id=chat_id, title=title)

    def set_chat_title_if_not_equals(self, chat_id, title):
        if title != self.vk.messages.getconversationById(
                peer_ids=2000000000 +
                chat_id)['items'][0]['chat_settings']['title']:
            self.vk.messages.editChat(chat_id=chat_id, title=title)

    def get_user_by_id(self, user_id):
        vk_user = VkUser.objects.filter(user_id=user_id)
        if len(vk_user) > 0:
            vk_user = vk_user.first()
        else:
            # Прозрачная регистрация
            user = self.vk.users.get(user_id=user_id,
                                     lang='ru',
                                     fields='sex, bdate, city, screen_name')[0]
            vk_user = VkUser()
            vk_user.user_id = user_id
            vk_user.name = user['first_name']
            vk_user.surname = user['last_name']
            if 'sex' in user:
                vk_user.gender = user['sex']
            if 'bdate' in user:
                vk_user.birthday = self.parse_date(user['bdate'])
            if 'city' in user:
                from apps.service.models import City
                city_name = user['city']['title']
                city = City.objects.filter(name=city_name)
                if len(city) > 0:
                    city = city.first()
                else:
                    try:
                        city = add_city_to_db(city_name)
                    except Exception:
                        city = None
                vk_user.city = city
            else:
                vk_user.city = None
            if 'screen_name' in user:
                vk_user.nickname = user['screen_name']
            vk_user.save()
            group_user = Group.objects.get(name=Role.USER.name)
            vk_user.groups.add(group_user)
            vk_user.save()
        return vk_user

    @staticmethod
    def get_gamer_by_user(user):
        from apps.games.models import Gamer

        gamers = Gamer.objects.filter(user=user)
        if len(gamers) == 0:
            gamer = Gamer(user=user)
            gamer.save()
            return gamer
        elif len(gamers) > 1:
            raise RuntimeWarning("Два и более игрока подходит под поиск")
        else:
            return gamers.first()

    @staticmethod
    def get_user_by_name(args, filter_chat=None):
        if not args:
            raise RuntimeWarning("Отсутствуют аргументы")
        if isinstance(args, str):
            args = [args]
        vk_users = VkUser.objects
        if filter_chat:
            vk_users = vk_users.filter(chats=filter_chat)
        if len(args) >= 2:
            user = vk_users.filter(name=args[0].capitalize(),
                                   surname=args[1].capitalize())
        else:
            user = vk_users.filter(nickname_real=args[0].capitalize())
            if len(user) == 0:
                user = vk_users.filter(name=args[0].capitalize())
                if len(user) == 0:
                    user = vk_users.filter(surname=args[0].capitalize())
                    if len(user) == 0:
                        user = vk_users.filter(nickname=args[0])
                        if len(user) == 0:
                            user = vk_users.filter(user_id=args[0])

        if len(user) > 1:
            raise RuntimeWarning("2 и более пользователей подходит под поиск")

        if len(user) == 0:
            raise RuntimeWarning(
                "Пользователь не найден. Возможно опечатка или он мне ещё ни разу не писал"
            )

        return user.first()

    @staticmethod
    def get_chat_by_name(args):
        if not args:
            raise RuntimeWarning("Отсутствуют аргументы")
        if isinstance(args, str):
            args = [args]
        vk_chats = VkChat.objects
        for arg in args:
            vk_chats = vk_chats.filter(name__icontains=arg)

        if len(vk_chats) > 1:
            raise RuntimeWarning("2 и более чатов подходит под поиск")

        if len(vk_chats) == 0:
            raise RuntimeWarning("Чат не найден")
        return vk_chats.first()

    def get_bot_by_id(self, bot_id):
        if bot_id > 0:
            bot_id -= 1
        vk_bot = VkBot.objects.filter(bot_id=bot_id)
        if len(vk_bot) > 0:
            vk_bot = vk_bot.first()
        else:
            # Прозрачная регистрация
            bot = self.vk.groups.getById(group_id=bot_id)[0]

            vk_bot = VkBot()
            vk_bot.bot_id = bot_id
            vk_bot.name = bot['name']
            vk_bot.save()

        return vk_bot

    @staticmethod
    def get_chat_by_id(chat_id):
        vk_chat = VkChat.objects.filter(chat_id=chat_id)
        if len(vk_chat) > 0:
            vk_chat = vk_chat.first()
        else:
            vk_chat = VkChat(chat_id=chat_id)
            vk_chat.save()
        return vk_chat

    @staticmethod
    def add_group_to_user(vk_user, chat):
        chats = vk_user.chats
        if chat not in chats.all():
            chats.add(chat)

    @staticmethod
    def remove_group_from_user(vk_user, chat):
        chats = vk_user.chats
        if chat in chats.all():
            chats.remove(chat)

    @staticmethod
    def get_group_id(_id):
        return 2000000000 + int(_id)

    def get_short_link(self, long_link):
        result = self.vk.utils.getShortLink(url=long_link)
        if 'short_url' in result:
            return result['short_url']
        else:
            return None

    def check_bot_working(self):
        return self.BOT_CAN_WORK

    @staticmethod
    def _prepare_obj_to_upload(file_like_object, allowed_exts_url=None):
        # bytes array
        if isinstance(file_like_object, bytes):
            obj = io.BytesIO(file_like_object)
            obj.seek(0)
        # bytesIO
        elif isinstance(file_like_object, io.BytesIO):
            obj = file_like_object
            obj.seek(0)
        # url
        elif urlparse(file_like_object).hostname:
            if allowed_exts_url:
                if file_like_object.split(
                        '.')[-1].lower() not in allowed_exts_url:
                    raise RuntimeWarning(
                        f"Загрузка по URL доступна только для {' '.join(allowed_exts_url)}"
                    )
            try:
                response = requests.get(file_like_object,
                                        stream=True,
                                        timeout=3)
            except SSLError:
                raise RuntimeWarning("SSLError")
            except requests.exceptions.ConnectionError:
                raise RuntimeWarning("ConnectionError")
            obj = response.raw
        # path
        else:
            obj = file_like_object
        return obj

    def upload_photos(self, images, max_count=10):
        if not isinstance(images, list):
            images = [images]

        attachments = []
        images_to_load = []
        for image in images:
            try:
                image = self._prepare_obj_to_upload(image,
                                                    ['jpg', 'jpeg', 'png'])
            except RuntimeWarning:
                continue
            except ReadTimeout:
                continue
            except ConnectTimeout:
                continue
            # Если Content-Length > 50mb
            bytes_count = None
            if isinstance(image, io.BytesIO):
                bytes_count = image.getbuffer().nbytes
            elif isinstance(
                    image, urllib3.response.HTTPResponse) or isinstance(
                        image,
                        requests.packages.urllib3.response.HTTPResponse):
                bytes_count = image.headers.get('Content-Length')
            elif os.path.exists(image):
                bytes_count = os.path.getsize(image)
            else:
                print("ШТО ТЫ ТАКОЕ", type(image))
            if not bytes_count:
                continue
            if int(bytes_count) / 1024 / 1024 > 50:
                continue
            images_to_load.append(image)

            if len(images_to_load) >= max_count:
                break
        try:
            vk_photos = self.upload.photo_messages(images_to_load)
            for vk_photo in vk_photos:
                attachments.append(
                    self.get_attachment_by_id('photo', vk_photo['owner_id'],
                                              vk_photo['id']))
        except vk_api.exceptions.ApiError as e:
            print(e)
        return attachments

    def upload_document(self, document, peer_id, title='Документ'):
        document = self._prepare_obj_to_upload(document)
        vk_document = self.upload.document_message(document,
                                                   title=title,
                                                   peer_id=peer_id)['doc']
        return self.get_attachment_by_id('doc', vk_document['owner_id'],
                                         vk_document['id'])

    def upload_audio(self, audio, artist, title):
        audio = self._prepare_obj_to_upload(audio)
        try:
            vk_audio = self.vk_user.upload.audio(audio,
                                                 artist=artist,
                                                 title=title)
        except vk_api.exceptions.ApiError as e:
            if e.code == 270:
                raise RuntimeError(
                    "Аудиозапись была удалена по просьбе правообладателя")
            raise RuntimeError("Ошибка загрузки аудиозаписи")
        return self.get_attachment_by_id('audio', vk_audio['owner_id'],
                                         vk_audio['id'])

    def upload_video_by_link(self, link, name):
        values = {
            'name': name,
            'is_private': True,
            'link': link,
        }

        response = self.vk_user.vk.video.save(**values)
        response2 = requests.post(response['upload_url']).json()
        print(response2)

        return f"video{response['owner_id']}_{response['video_id']}"

    def get_attachment_by_id(self, _type, group_id, _id):
        if group_id is None:
            group_id = f'-{self.group_id}'
        return f"{_type}{group_id}_{_id}"
Example #7
0
def send_message(uid,
                 chat_id,
                 msg=None,
                 photo=None,
                 documents=None,
                 audio=None,
                 voice=None,
                 video=None,
                 sticker=None):
    try:
        session = get_session(uid)
        api = get_api(uid)
    except IndexError:
        return 0

    # Generate message id like in vk api
    msg_id = randint(1, 9223372036854775700)
    try:
        vchat_id = execute(
            f"select vchat_id from chats where chat_id = {chat_id}")[0][0]
        # if chat is conference
        if vchat_id is None:
            vchat_id = execute(
                f"select peer_id from chats where chat_id = {chat_id}")[0][0]
    except IndexError:
        # If telegram group not binded to vk chat stream
        return

    attachments = []
    if photo or documents or audio or voice or video or sticker:
        if photo:
            attach = photo
        elif documents:
            attach = documents
        elif audio:
            attach = audio
        elif voice:
            attach = voice
        elif video:
            attach = video
        elif sticker:
            attach = sticker
        else:
            attach = None

        uploader = VkUpload(session)
        attach_file = attach.get_file().download()
        if sticker:
            new_name = attach_file.split('.')[0] + '.png'
            try:
                im = Image.open(attach_file).convert("RGBA")
                im.save(new_name, "png")
            except UnidentifiedImageError:
                # Animated sticker
                return
            attach_file = new_name
        attach_file = open(attach_file, 'rb')
        file_name = attach_file.name

        if photo:
            upload_response = uploader.photo_messages(photos=attach_file,
                                                      peer_id=vchat_id)
            for file in upload_response:
                attachments.append(f"photo{file['owner_id']}_{file['id']}")

        if audio:
            _msg = "VK doesn't allow to upload music."
            if msg is None:
                msg = _msg
            else:
                msg += "\n" + _msg

        if voice:
            upload_response = uploader.audio_message(audio=attach_file,
                                                     peer_id=vchat_id)
            audio = upload_response['audio_message']
            attachments.append(
                f"audio_message{audio['owner_id']}_{audio['id']}")

        if documents:
            upload_response = uploader.document_message(attach_file,
                                                        peer_id=vchat_id)
            doc = upload_response['doc']
            attachments.append(f"doc{doc['owner_id']}_{doc['id']}")

        if video:
            video = uploader.video(attach_file, is_private=True)
            attachments.append(f"video{video['owner_id']}_{video['video_id']}")

        if sticker:
            upload_response = uploader.graffiti(attach_file, peer_id=vchat_id)
            graffiti = upload_response['graffiti']
            attachments.append(f"doc{graffiti['owner_id']}_{graffiti['id']}")

        attach_file.close()
        remove(file_name)

        attachments = ','.join(attachments)

    if msg is not None:
        api.messages.send(random_id=msg_id,
                          message=msg,
                          peer_id=vchat_id,
                          attachment=attachments)
    else:
        api.messages.send(random_id=msg_id,
                          peer_id=vchat_id,
                          attachment=attachments)

    api.account.setOffline()