Esempio n. 1
0
def token(bot, message):
    try:
        message = message.message
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        asyncio.get_event_loop().run_until_complete(
            redis.execute("HSET", "user:{0}".format(message.from_user.id),
                          "active", "True", "VK_TOKEN",
                          message.text.split(" ")[1]))
        asyncio.get_event_loop().run_until_complete(
            redis.execute("SADD", "users",
                          "user:{0}".format(message.from_user.id)))

        bot.send_message(
            message.from_user.id,
            "Отлично! Я запомнил твой токен доступа VK, теперь буду пересылать "
            "сообщения оттуда. Спасибо, что используешь меня!")
    except Exception as e:
        try:
            bot.send_message(
                message.from_user.id,
                "❗ Произошла непредвиденная ошибка при выполнении метода. Сообщите об этом "
                "администратору для более быстрого ее исправления.")
        except:
            pass

        logging.error("Произошла ошибка при попытке выполнения метода.",
                      exc_info=True)
        return e
Esempio n. 2
0
def callback(bot, call):
    try:
        call = call.callback_query
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        if call.message:
            if call.data.startswith("channel_counters"):
                counter = call.data.split("|", 2)

                if counter[1] == "time":
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="🕒 Время публикации данного поста: {0} MSK.".format(
                                                  str(datetime.fromtimestamp(
                                                      int(counter[2])).strftime("%d.%m.%y, %H:%M:%S"))),
                                              show_alert=True, cache_time=30)
                elif counter[1] == "likes":
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="💖 Количество лайков: {0}.".format(
                                                  str(counter[2])), show_alert=True, cache_time=30)
                elif counter[1] == "comments":
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="💬 Количество комментариев: {0}.".format(
                                                  str(counter[2])), show_alert=True, cache_time=30)
                elif counter[1] == "reposts":
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="🔁 Количество репостов: {0}.".format(
                                                  str(counter[2])), show_alert=True, cache_time=30)
                elif counter[1] == "views":
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="👁 Количество просмотров: {0}.".format(
                                                  str(counter[2])), show_alert=True, cache_time=30)
                elif counter[1] == "poll":
                    poll = loop.run_until_complete(redis.execute("GET", str("poll&" + str(counter[2]))))
                    if not poll:
                        logging.debug("Poll Name is None, most likely this poll isn't in the cache.")
                        refresh_stats(bot, call, expired=1)
                        return
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="📋 Название голосования: {0}.".format(
                                                  str(poll[0:170])), show_alert=True, cache_time=30)
                elif counter[1] == "poll_ans":
                    poll_answer = loop.run_until_complete(redis.execute("GET", str("poll_answer&" + str(counter[2]))))
                    if not poll_answer:
                        logging.debug("Poll Answer is None, most likely this poll isn't in the cache.")
                        refresh_stats(bot, call, expired=1)
                        return
                    else:
                        poll_answer = poll_answer.split("?|&|&|!", 1)
                    bot.answer_callback_query(callback_query_id=call.id,
                                              text="❎ Количество голосов за {0}: {1} голосов.".format(
                                                  str(poll_answer[0][0:140]), str(poll_answer[1])),
                                              show_alert=True, cache_time=30)
            elif call.data.startswith("channel_refresh_stats"):
                refresh_stats(bot, call)
            bot.answer_callback_query(callback_query_id=call.id, show_alert=False)
    except Exception as e:
        logging.error("Exception has been occurred while trying to execute the method.", exc_info=True)
        return e
Esempio n. 3
0
def start(bot, message):
    try:
        message = message.message
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        if "ru" not in message.from_user.language_code:
            bot.send_message(
                message.from_user.id,
                "❗ Unfortunately, the bot doesn't speak your language. So if you are "
                "not able to understand Russian, use an online translator such as Google Translate."
            )

        bot.send_message(message.from_user.id,
                         config.startMessage,
                         parse_mode="Markdown")
        loop.run_until_complete(
            redis.execute(
                "SET", "status:{0}".format(message.from_user.id),
                '{"status": "waiting", "method": "find_communities"}', "EX",
                180))
    except Exception as e:
        try:
            bot.send_message(
                message.from_user.id,
                "❗ Извините, что-то пошло не так, но в скором времени все исправится. "
                "Попробуйте выполнить то же самое действие через некоторое время (10-15 минут)."
            )
        except:
            pass

        logging.error(
            "Exception has been occurred while trying to execute the method.",
            exc_info=True)
        return e
Esempio n. 4
0
def start_polling(client):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        users = asyncio.get_event_loop().run_until_complete(
            redis.execute("SMEMBERS", "users"))['details']
        logging.debug("Пользователи в Redis: " + str(users))
        while True:
            for user in users:
                user_id = user
                user = asyncio.get_event_loop().run_until_complete(
                    hgetall(user))
                if parse_as_boolean(user['active']):
                    # TODO: Использовать Dramatiq вместо этого самопального кода
                    result = poll_user(user, user_id, client)
                    logging.debug(
                        "Выполнен polling пользователя {0}, результат: {1}".
                        format(user_id, result))

            sleep(0.1)
    except Exception as e:
        logging.error(
            "Произошла ошибка при попытке начала polling'а всех аккаунтов VK.",
            exc_info=True)
        return e
Esempio n. 5
0
def message_handler(client, message):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        try:
            assert message.reply_to_message
        except AssertionError:
            return
        tg_message_id = "{0}_{1}".format(message.reply_to_message.chat.id,
                                         message.reply_to_message.message_id)
        vk_message_id = asyncio.get_event_loop().run_until_complete(
            redis.execute(
                "GET",
                "message:telegram:{0}".format(tg_message_id)))['details']
        if not vk_message_id:
            logging.debug(
                "Сообщение, на которое ответил пользователь, не содержит ID сообщения из VK."
            )
            if config.DEVELOPER_MODE:
                message.reply(
                    "ℹ️ К сообщению, на которое вы ответили, не привязано ID сообщения из VK.",
                    disable_notification=True)
            return

        user = asyncio.get_event_loop().run_until_complete(
            hgetall("user:{0}".format(message.from_user.id)))
        peer_id = vk_message_id.split("_")[0]

        try:
            assert message.sticker
            parse_sticker(client, message.sticker.file_id, user['VK_TOKEN'],
                          user['VK_SECRET'], peer_id)
        except AssertionError:
            logging.debug("Похоже, что это сообщение не является стикером..")

        if message.text:
            data = {
                "peer_id": peer_id,
                "random_id": randbits(32),
                "message": message.text,
                "access_token": user['VK_TOKEN'],
                "v": 5.92
            }
            response = requests.post("https://api.vk.com/method/messages.send",
                                     data=sign_data(data, "messages.send",
                                                    user['VK_SECRET'])).json()
            logging.debug(
                "Сообщение было отправлено, VK вернул ответ: {0}".format(
                    response))
            if config.DEVELOPER_MODE:
                message.reply(
                    "ℹ️ Сообщение было отправлено, VK вернул ответ:\n\n`{0}`.".
                    format(response),
                    disable_notification=True)
    except Exception as e:
        logging.error("Произошла ошибка при попытке выполнения метода.",
                      exc_info=True)
        return e
Esempio n. 6
0
def store(client, message):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        item_store_file, item_store_hash = asyncio.get_event_loop(
        ).run_until_complete(parse_item_store())
        item_store_file_id = asyncio.get_event_loop().run_until_complete(
            Redis.execute("GET", "fortnite:store:file_id:{0}".format(
                item_store_hash)))['details']

        if item_store_file_id and not config.DEVELOPER_MODE:
            logging.info(
                "Изображение текущего магазина предметов уже было загружено в Telegram, "
                "File ID: {0}.".format(item_store_file_id))

            client.send_photo(
                message.chat.id,
                item_store_file_id,
                caption=
                "🛒 Текущий магазин предметов в Фортнайте."
            )
        else:
            message = client.send_photo(
                message.chat.id,
                item_store_file,
                caption=
                "🛒 Текущий магазин предметов в Фортнайте."
            )
            item_store_file_id = message['photo']['sizes'][-1]['file_id']

            asyncio.get_event_loop().run_until_complete(
                Redis.execute(
                    "SET",
                    "fortnite:store:file_id:{0}".format(item_store_hash),
                    item_store_file_id, "EX", 86400))
    except Exception as e:
        logging.error(
            "Произошла ошибка при выполнении команды /store.",
            exc_info=True)
        return e
Esempio n. 7
0
def message(bot, message):
    try:
        message = message.message
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        if not message.text.startswith("/"):
            status = loop.run_until_complete(
                redis.execute("GET",
                              "status:{0}".format(message.from_user.id)))
            if status:
                status = json.loads(str(status))
                if status['method'] == "find_communities":
                    find_community(bot, message)
                    loop.run_until_complete(
                        redis.execute(
                            "DEL", "status:{0}".format(message.from_user.id)))
    except Exception as e:
        logging.error(
            "Exception has been occurred while trying to execute the method.",
            exc_info=True)
        return e
Esempio n. 8
0
def news(client, message):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        news_file, news_hash = asyncio.get_event_loop().run_until_complete(
            parse_news())
        news_file_id = asyncio.get_event_loop().run_until_complete(
            Redis.execute(
                "GET",
                "fortnite:news:file_id:{0}".format(news_hash)))['details']

        if news_file_id and not config.DEVELOPER_MODE:
            logging.info(
                "Изображение текущих новостей уже было загружено в Telegram, "
                "File ID: {0}.".format(news_file_id))

            client.send_photo(
                message.chat.id,
                news_file_id,
                caption=
                "📰 Текущие новости Королевской Битвы и Сражения с Бурей.")
        else:
            message = client.send_photo(
                message.chat.id,
                news_file,
                caption=
                "📰 Текущие новости Королевской Битвы и Сражения с Бурей.")
            news_file_id = message['photo']['sizes'][-1]['file_id']

            asyncio.get_event_loop().run_until_complete(
                Redis.execute("SET",
                              "fortnite:news:file_id:{0}".format(news_hash),
                              news_file_id, "EX", 86400))
    except Exception as e:
        logging.error("Произошла ошибка при выполнении команды /news.",
                      exc_info=True)
        return e
Esempio n. 9
0
def cancel(bot, message):
    try:
        message = message.message
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        loop.run_until_complete(
            redis.execute("DEL", "status:{0}".format(message.from_user.id)))
        bot.send_message(message.from_user.id,
                         "Вы успешно отменили текущую операцию.")
    except Exception as e:
        try:
            bot.send_message(
                message.from_user.id,
                "❗ Извините, что-то пошло не так, но в скором времени все будет исправлено. "
                "Попробуйте выполнить то же самое действие через некоторое время (10-15 минут)."
            )
        except:
            pass

        logging.error(
            "Exception has been occurred while trying to execute the method.",
            exc_info=True)
        return e
Esempio n. 10
0
def activate(bot, message):
    try:
        message = message.message
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        asyncio.get_event_loop().run_until_complete(
            redis.execute("HSET", "users:{0}".format(message.from_user.id),
                          "active", "True"))

        bot.send_message(message.from_user.id,
                         "Хорошо! Я изменил твой статус на активный.")
    except Exception as e:
        try:
            bot.send_message(
                message.from_user.id,
                "❗ Произошла непредвиденная ошибка при выполнении метода. Сообщите об этом "
                "администратору для более быстрого ее исправления.")
        except:
            pass

        logging.error("Произошла ошибка при попытке выполнения метода.",
                      exc_info=True)
        return e
Esempio n. 11
0
async def redis_hgetall(key):
    return dict(
        zip_longest(
            *[iter((await (redis.execute("HGETALL", key)))['details'])] * 2,
            fillvalue=""))
Esempio n. 12
0
def parse_sticker(client, file_id, access_token, sig_secret, peer_id):
    logging.debug("Обрабатывается стикер с File ID: {0}...".format(file_id))
    sticker_hash = sha1(file_id.encode("UTF-8")).hexdigest()
    sticker_id = asyncio.get_event_loop().run_until_complete(
        redis.execute("GET",
                      "files:vk:sticker:{0}".format(sticker_hash)))['details']

    if not sticker_id:
        logging.debug(
            "Стикер не содержится в кэше, выполняются необходимые процессы...".
            format(sticker_hash))
        with NamedTemporaryFile(suffix=".webp") as sticker_webp_file:
            logging.debug("Стикер скачивается во временный файл {1}...".format(
                file_id, sticker_webp_file.name))
            client.download_media(file_id, sticker_webp_file.name)
            sticker_webp = Image.open(sticker_webp_file.name)
            with NamedTemporaryFile(suffix=".png") as sticker_png_file:
                sticker_webp.save(sticker_png_file.name, format="PNG")
                logging.debug(
                    "Стикер переконвертирован в формат PNG, сохранен в файл: {0}"
                    .format(sticker_png_file.name))

                data = {
                    "type": "graffiti",
                    "access_token": access_token,
                    "v": 5.92
                }
                upload_server = requests.post(
                    "https://api.vk.com/method/docs.getUploadServer",
                    data=sign_data(data, "docs.getUploadServer",
                                   sig_secret)).json()
                logging.debug(
                    "URL сервера VK, на который будет происходить загрузка стикера: {0}"
                    .format(upload_server['response']['upload_url']))

                files = {'file': open(sticker_png_file.name, 'rb')}
                uploaded_file = requests.post(
                    upload_server['response']['upload_url'],
                    files=files).json()
                logging.debug(
                    "После загрузки стикера на сервер VK, получен ответ: {0}".
                    format(uploaded_file))

                data = {
                    "file": uploaded_file['file'],
                    "access_token": access_token,
                    "v": 5.92
                }
                saved_document = requests.post(
                    "https://api.vk.com/method/docs.save",
                    data=sign_data(data, "docs.save", sig_secret)).json()
                logging.debug(
                    "Стикер сохранен как документ, получен ответ: {0}".format(
                        saved_document))

                sticker_id = "doc{0}_{1}_{2}".format(
                    saved_document['response']['graffiti']['owner_id'],
                    saved_document['response']['graffiti']['id'],
                    saved_document['response']['graffiti']['access_key'])
                asyncio.get_event_loop().run_until_complete(
                    redis.execute("SET",
                                  "files:vk:sticker:{0}".format(sticker_hash),
                                  sticker_id))

    data = {
        "peer_id": peer_id,
        "random_id": randbits(32),
        "attachment": sticker_id,
        "access_token": access_token,
        "v": 5.92
    }
    sent_message = requests.post("https://api.vk.com/method/messages.send",
                                 data=sign_data(data, "messages.send",
                                                sig_secret)).json()
    logging.debug(
        "Сообщение со стикером было отправлено, получен ответ: {0}".format(
            sent_message))
Esempio n. 13
0
def statistics(bot, posts, chat_id, mtype="initiate", message_id=None):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        poll = None

        try:
            try:
                posts['attachments']

                poll = []

                for anum in range(len(posts['attachments'])):
                    if posts['attachments'][anum]['type'] == "poll":
                        poll.extend([{"question": str(posts['attachments'][anum]['poll']['question']),
                                      "answers": literal_eval(
                                          str(posts['attachments'][anum]['poll']['answers']))}])
            except KeyError:
                logging.debug("KeyError Exception has been occurred, most likely the post doesn't have "
                              "any attachments.", exc_info=True)
        except Exception as e:
            logging.error("Exception has been occurred while trying to execute the method.", exc_info=True)

        for elm in ["time", "likes", "comments", "reposts", "views"]:
            if elm is "time":
                pass
            else:
                try:
                    posts[elm]['count']
                except KeyError:
                    posts[elm] = {"count": 0}

        markup = []

        markup.extend([
            [InlineKeyboardButton("🕒 {0}".format(
                str(datetime.fromtimestamp(int(posts['date'])).strftime("%d.%m.%y, %H:%M:%S"))),
                callback_data="channel_counters|time|{0}".format(str(posts['date'])))]
        ])
        markup.extend([[
            InlineKeyboardButton("💖 {0}".format(
                (str(round(int(posts['likes']['count']) / 1000.0) * 1) + "K" if int(posts['likes']['count']) > 1000
                 else str(posts['likes']['count']))),
                callback_data="channel_counters|likes|{0}".format(str(posts['likes']['count']))),
            InlineKeyboardButton("💬 {0}".format(
                (str(round(int(posts['comments']['count']) / 1000.0) * 1) + "K"
                 if int(posts['comments']['count']) > 1000 else str(posts['comments']['count']))),
                callback_data="channel_counters|comments|{0}".format(str(posts['comments']['count']))),
            InlineKeyboardButton("🔁 {0}".format(
                (str(round(int(posts['reposts']['count']) / 1000.0) * 1) + "K" if int(posts['reposts']['count']) > 1000
                 else str(posts['reposts']['count']))),
                callback_data="channel_counters|reposts|{0}".format(str(posts['reposts']['count']))),
            InlineKeyboardButton("👁️ {0}".format(
                (str(round(int(posts['views']['count']) / 1000.0) * 1) + "K" if int(posts['views']['count']) > 1000
                 else str(posts['views']['count']))),
                callback_data="channel_counters|views|{0}".format(str(posts['views']['count']))),
        ]])

        if poll:
            poll_uuid = uuid4()
            try:
                poll[0]['question'][31]
                poll_question = str(poll[0]['question'][0:30]) + "..."
            except IndexError:
                poll_question = str(poll[0]['question'][0:30])

            markup.extend([
                [InlineKeyboardButton("📋 {0}".format(
                    str(poll_question)),
                    callback_data="channel_counters|poll|{0}".format(str(poll_uuid)))]])
            loop.run_until_complete(redis.execute("SET", str("poll&" + str(poll_uuid)), str(poll[0]['question'])))
            loop.run_until_complete(redis.execute("EXPIRE", str("poll&" + str(poll_uuid)), 900))
            logging.debug("Poll UUID: " + str(poll_uuid))

            for pint in range(len(poll[0]['answers'])):
                pollanswer_uuid = uuid4()
                try:
                    poll[0]['answers'][pint]['text'][31]
                    poll_question = str(poll[0]['answers'][pint]['text'][0:30]) + "..."
                except IndexError:
                    poll_question = str(poll[0]['answers'][pint]['text'][0:30])

                markup.extend([[
                    InlineKeyboardButton("❎ {0} — {1} 👍🏻".format(
                        str(poll_question),
                        (str(round(int(poll[0]['answers'][pint]['votes']) / 1000.0) * 1) + "K" if int(
                            poll[0]['answers'][pint]['votes']) > 1000
                         else str(poll[0]['answers'][pint]['votes']))),
                        callback_data="channel_counters|poll_ans|{0}".format(
                            str(pollanswer_uuid)))]])
                loop.run_until_complete(redis.execute("SET", str("poll_answer&" + str(pollanswer_uuid)), str(
                    str(poll[0]['answers'][pint]['text']) + "?|&|&|!" + str(poll[0]['answers'][pint]['votes']))))
                loop.run_until_complete(redis.execute("EXPIRE", str("poll_answer&" + str(pollanswer_uuid)), 1800))
                logging.debug("Poll Answer UUID: " + str(pollanswer_uuid))

        markup.extend([
            [InlineKeyboardButton("🔄 Обновить статистику",
                                  callback_data="channel_refresh_stats|{0}".format(
                                      str(int(int(time())) + 600)))]])

        markup = InlineKeyboardMarkup(markup)

        if mtype == "initiate":
            return markup
        elif mtype == "update":
            try:
                bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=markup)
                return "OK"
            except BadRequest as e:
                if "Message is not modified" in str(e):
                    return "IS NOT MODIFIED"
                else:
                    logging.error("Exception BadRequest has been occurred while trying to edit message markup.",
                                  exc_info=True)
                    return "ERROR"
    except Exception as e:
        logging.error("Exception has been occurred while trying to execute the method.", exc_info=True)
        return e
Esempio n. 14
0
def poll_user(user, user_id, client):
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        telegram_user_id = int(user_id.split(":")[1])
        logging.debug("Выполняется polling пользователя c ID: {0}".format(user_id))

        try:
            assert user['VK_TOKEN']
            assert user['VK_SECRET']
        except (AssertionError, KeyError, TypeError):
            return {"status": "ERROR", "details": "У пользователя указан неверный VK токен (или он отстутствует)"}

        try:
            assert user['VK_LP_KEY']
            assert user['VK_LP_SERVER']
            assert user['VK_LP_PTS']
        except (AssertionError, KeyError, TypeError):
            logging.debug("У пользователя {0} нет данных о LongPoll сервере.".format(user_id))

            data = {"need_pts": 1, "lp_version": 3, "access_token": user['VK_TOKEN'], "v": 5.92}
            response_lps = requests.post("https://api.vk.com/method/messages.getLongPollServer",
                                         data=sign_data(data, "messages.getLongPollServer", user['VK_SECRET'])).json()
            logging.debug("Ответ на запрос метода messages.getLongPollServer: " + str(response_lps))
            response_lps = response_lps['response']

            user['VK_LP_KEY'] = response_lps['key']
            user['VK_LP_SERVER'] = response_lps['server']
            user['VK_LP_PTS'] = response_lps['pts']
            asyncio.get_event_loop().run_until_complete(
                redis.execute("HSET", user_id, "VK_LP_KEY", response_lps['key'], "VK_LP_SERVER", response_lps['server'],
                              "VK_LP_PTS", response_lps['pts']))

        data = {"key": user['VK_LP_KEY'], "server": user['VK_LP_SERVER'], "pts": user['VK_LP_PTS'],
                "fields": "screen_name", "mode": 2, "lp_version": 3, "access_token": user['VK_TOKEN'], "v": 5.92}
        response_lph = requests.post("https://api.vk.com/method/messages.getLongPollHistory",
                                     data=sign_data(data, "messages.getLongPollHistory", user['VK_SECRET'])).json()
        logging.debug("Ответ на запрос метода messages.getLongPollHistory: " + str(response_lph))
        response_lph = response_lph['response']

        for message in response_lph['messages']['items']:
            if int(message['out']) == 1:
                continue
            message_id = "{0}_{1}".format(message['peer_id'], message['conversation_message_id'])

            # Проверяем сообщение на наличие вложений в сообщении
            media = []
            # TODO: Добавить chat_action (загрузка видео, аудио)
            for attachment in message['attachments']:
                if attachment['type'] == "photo":
                    photo_sorted_sizes = sorted(attachment['photo']['sizes'], key=itemgetter('width'))
                    with NamedTemporaryFile(suffix=".jpg", delete=False) as photo:
                        photo.write(requests.get(photo_sorted_sizes[-1]['url'], stream=True).content)
                        media.extend([InputMediaPhoto(photo.name)])
                if attachment['type'] == "video":
                    # Получаем видео, которое сможем загрузить при лимите в 1.5 ГБ
                    video_url = None
                    for video_quality in attachment['video']['files']:
                        if video_quality == "hls" or video_quality == "external":
                            continue
                        video_size = int(requests.get(attachment['video']['files'][video_quality], stream=True).
                                         headers['Content-length'])
                        if video_size < 1500 * 1024 * 1024:
                            video_url = attachment['video']['files'][video_quality]
                    if not video_url:
                        continue

                    video_hash = sha1(video_url.split("?extra")[0].encode("UTF-8")).hexdigest()
                    video = asyncio.get_event_loop().run_until_complete(
                        hgetall("files:telegram:video:{0}".format(video_hash)))

                    if not video:
                        logging.debug("Видео ({0}) не найдено в кэше, загружается новое.".format(video_hash))
                        with NamedTemporaryFile(suffix=".mp4") as video_file:
                            logging.debug("Видео ({0}) сохраняется во временный файл {1}.".format(
                                video_hash, video_file.name))
                            video_file.write(requests.get(video_url, stream=True).content)

                            # Отправляем видео и сразу удаляем (это необходимо, чтобы получить File ID видео)
                            video_message = client.send_video(telegram_user_id, video_file.name,
                                                              disable_notification=True)
                            client.delete_messages(telegram_user_id, video_message.message_id)

                            video['FILE_ID'] = video_message.video.file_id
                            video['CAPTION'] = attachment['video']['title']

                        asyncio.get_event_loop().run_until_complete(redis.execute(
                            "HSET", "files:telegram:video:{0}".format(video_hash), "FILE_ID", video['FILE_ID'],
                            "CAPTION", video['CAPTION']))

                    media.extend([InputMediaVideo(video['FILE_ID'], caption=video['CAPTION'])])
                # TODO: Добавить поддержку плейлистов (корректное отображение)
                if attachment['type'] == "audio":
                    audio_hash = sha1(attachment['audio']['url'].encode("UTF-8")).hexdigest()
                    audio = asyncio.get_event_loop().run_until_complete(
                        redis.execute("HGET", "files:telegram:audio:{0}".format(audio_hash), "FILE_ID"))['details']

                    # В Redis нет смысла сохранять исполнителя и название песни, так как при последующей отправке по
                    # File ID, данные об исполнителе и названии песни остаются.
                    if audio:
                        logging.debug("Аудио ({0}) находится в кэше, отправляется по File ID.".format(audio_hash))
                        client.send_audio(telegram_user_id, audio)
                    else:
                        logging.debug("Аудио ({0}) не найдено в кэше, загружается новое.".format(audio_hash))
                        # VK может вернуть пустое URL, проверяем это сначала
                        if not attachment['audio']['url']:
                            logging.debug("Аудио, которое было отправлено пользователю, не может быть загружено, "
                                          "так как в нем не содержится ссылки (скорее всего, оно защищено "
                                          "авторскими правами и может воспроизводиться только из РФ).")
                            client.send_message(telegram_user_id,
                                                "❗ Аудио ({0} — {1}) не может быть отправлено вам, так как "
                                                "оно защищено авторскими правами и может воспроизводиться только с "
                                                "территории Российской Федерации.".format(
                                                    attachment['audio']['title'], attachment['audio']['artist']))
                            continue

                        with NamedTemporaryFile(suffix=".mp3") as audio_file:
                            logging.debug("Аудио ({0}) сохраняется во временный файл {1}.".format(
                                audio_hash, audio_file.name))
                            audio_file.write(requests.get(attachment['audio']['url'], stream=True).content)
                            audio = client.send_audio(telegram_user_id, audio_file.name,
                                                      performer=attachment['audio']['artist'],
                                                      title=attachment['audio']['title']).audio.file_id

                        asyncio.get_event_loop().run_until_complete(
                            redis.execute("HSET", "files:telegram:audio:{0}".format(audio_hash), "FILE_ID", audio))
                if attachment['type'] == "sticker":
                    sticker_hash = sha1(attachment['sticker']['images'][4]['url'].encode("UTF-8")).hexdigest()
                    sticker = asyncio.get_event_loop().run_until_complete(
                        redis.execute("HGET", "files:telegram:sticker:{0}".format(sticker_hash), "FILE_ID"))['details']

                    if sticker:
                        logging.debug("Стикер ({0}) находится в кэше, отправляется по File ID.".format(sticker_hash))
                        client.send_sticker(telegram_user_id, sticker)
                    else:
                        logging.debug("Стикер ({0}) не найден в кэше, загружается новый.".format(sticker_hash))
                        sticker_png = Image.open(
                            BytesIO(requests.get(attachment['sticker']['images'][4]['url'], stream=True).content))

                        with NamedTemporaryFile() as sticker_file:
                            logging.debug("Стикер ({0}) сохраняется во временный файл {1}.".format(
                                sticker_hash, sticker_file.name))
                            sticker_png.save(sticker_file, format="WEBP", lossless=True, quality=100, method=6)
                            sticker = client.send_sticker(telegram_user_id, sticker_file.name).sticker.file_id

                        asyncio.get_event_loop().run_until_complete(
                            redis.execute("HSET", "files:telegram:sticker:{0}".format(sticker_hash), "FILE_ID", sticker))
                if attachment['type'] == "audio_message":
                    with NamedTemporaryFile(suffix=".ogg") as voice_message_file:
                        logging.debug("Голосовое сообщение сохраняется во временный файл {0}.".format(
                            voice_message_file.name))
                        voice_message_file.write(
                            requests.get(attachment['audio_message']['link_ogg'], stream=True).content)
                        client.send_voice(telegram_user_id, voice_message_file.name)

            # Проверяем, есть ли какое-то медиа (фотографии, видео)
            if media:
                client.send_media_group(telegram_user_id, media)

            sender = [sender for sender in response_lph['profiles'] if sender['id'] == message['from_id']][0]
            formatted_message_text = markup_multipurpose_fixes(message['text'])
            message_text = "**{0} {1}**{2}".format(sender['first_name'], sender['last_name'],
                                                   # Если есть текст в сообщении, то добавляем его в сообщении
                                                   "\n\n" + formatted_message_text if formatted_message_text else "")

            markup = InlineKeyboardMarkup([[InlineKeyboardButton("📋 Подробнее", callback_data=b"TEST")]])
            message_data = client.send_message(telegram_user_id, message_text, reply_markup=markup)

            asyncio.get_event_loop().run_until_complete(
                redis.execute("SET", "message:telegram:{0}_{1}".format(message_data.chat.id, message_data.message_id),
                              message_id)
            )

        asyncio.get_event_loop().run_until_complete(redis.execute("HSET", user_id,
                                                                  "VK_LP_PTS", response_lph['new_pts']))
        return {"status": "OK", "details": None}
    except Exception as e:
        logging.error("Произошла ошибка при polling'е аккаунта VK пользователя {0}.".format(user_id), exc_info=True)
        return e