Example #1
0
def handle():
    """
    Handles `/settings` command.
    """
    user = g.db_user
    incoming_chat = g.db_chat
    private_chat = g.db_private_chat

    if (private_chat is None):
        return request_private_chat(incoming_chat.telegram_id)

    yd_access = False

    if ((user.yandex_disk_token)
            and (user.yandex_disk_token.have_access_token())):
        yd_access = True

    text = ("<b>Preferred language:</b> "
            f"{user.language.name}"
            "\n"
            "<b>Yandex.Disk Access:</b> "
            f"{'Given' if yd_access else 'Revoked'}")

    telegram.send_message(chat_id=private_chat.telegram_id,
                          parse_mode="HTML",
                          text=text)
def cancel_command(chat_telegram_id: int,
                   edit_message: int = None,
                   reply_to_message: int = None) -> None:
    """
    Cancels command execution due to internal server error.

    - don't confuse with `abort_command()`.
    - if `edit_message` Telegram ID specified, then
    that message will be edited.
    - if `reply_to_message` Telegram ID specified, then
    that message will be used for reply message.
    """
    text = ("At the moment i can't process this "
            "because of my internal error. "
            "Try later please.")

    if (edit_message is not None):
        telegram.edit_message_text(chat_id=chat_telegram_id,
                                   message_id=edit_message,
                                   text=text)
    elif (reply_to_message is not None):
        telegram.send_message(chat_id=chat_telegram_id,
                              reply_to_message_id=reply_to_message,
                              text=text)
    else:
        telegram.send_message(chat_id=chat_telegram_id, text=text)
Example #3
0
def message_have_access_token(chat_id: int) -> None:
    telegram.send_message(
        chat_id=chat_id,
        text=("You already grant me access to your Yandex.Disk."
              "\n"
              "You can revoke my access with "
              f"{CommandName.YD_REVOKE.value}"))
Example #4
0
def handle(*args, **kwargs):
    """
    Handles `/settings` command.
    """
    private_chat = g.db_private_chat

    if private_chat is None:
        incoming_chat_id = kwargs.get(
            "chat_id",
            g.db_chat.telegram_id
        )

        return request_private_chat(incoming_chat_id)

    user = g.db_user
    yo_client = YandexOAuthClient()
    yd_access = False

    if yo_client.have_valid_access_token(user):
        yd_access = True

    text = (
        "<b>Preferred language:</b> "
        f"{user.language.name}"
        "\n"
        "<b>Yandex.Disk Access:</b> "
        f"{'Given' if yd_access else 'Revoked'}"
    )

    telegram.send_message(
        chat_id=private_chat.telegram_id,
        parse_mode="HTML",
        text=text
    )
Example #5
0
def handle(*args, **kwargs):
    """
    Handles `/about` command.
    """
    telegram.send_message(
        chat_id=kwargs.get("chat_id", g.telegram_chat.id),
        disable_web_page_preview=True,
        text=("I'm free and open-source bot that allows "
              "you to interact with Yandex.Disk through Telegram."
              "\n\n"
              f"Written by {current_app.config['PROJECT_AUTHOR']}"),
        reply_markup={
            "inline_keyboard":
            [[{
                "text": "Source code",
                "url": current_app.config['PROJECT_URL_FOR_CODE']
            }],
             [{
                 "text": "Report a problem",
                 "url": current_app.config["PROJECT_URL_FOR_ISSUE"]
             }, {
                 "text": "Request a feature",
                 "url": current_app.config["PROJECT_URL_FOR_REQUEST"]
             }, {
                 "text": "Ask a question",
                 "url": current_app.config["PROJECT_URL_FOR_QUESTION"]
             }],
             [{
                 "text": "Privacy Policy",
                 "url": absolute_url_for("legal.privacy_policy")
             }, {
                 "text": "Terms of service",
                 "url": absolute_url_for("legal.terms_and_conditions")
             }]]
        })
def handle():
    """
    Handles `/help` command.
    """
    yd_upload_default_folder = current_app.config[
        "YANDEX_DISK_API_DEFAULT_UPLOAD_FOLDER"]

    text = (
        "You can control me by sending these commands:"
        "\n\n"
        "<b>Yandex.Disk</b>"
        "\n"
        f'For uploading "{to_code(yd_upload_default_folder)}" folder is used by default.'
        "\n"
        f"{CommandsNames.UPLOAD_PHOTO.value} — upload a photo. "
        "Original name will be not saved, quality of photo will be decreased. "
        "You can send photo without this command."
        "\n"
        f"{CommandsNames.UPLOAD_FILE.value} — upload a file. "
        "Original name will be saved. "
        "For photos, original quality will be saved. "
        "You can send file without this command."
        "\n"
        f"{CommandsNames.UPLOAD_AUDIO.value} — upload an audio. "
        "Original name will be saved, original type may be changed. "
        "You can send audio file without this command."
        "\n"
        f"{CommandsNames.UPLOAD_VIDEO.value} — upload a video. "
        "Original name will be not saved, original type may be changed. "
        "You can send video file without this command."
        "\n"
        f"{CommandsNames.UPLOAD_VOICE.value} — upload a voice. "
        "You can send voice file without this command."
        "\n"
        f"{CommandsNames.UPLOAD_URL.value} — upload a file using direct URL. "
        "Original name will be saved. "
        "You can send direct URL to a file without this command."
        "\n"
        f"{CommandsNames.CREATE_FOLDER.value}— create a folder. "
        "Send folder name to create with this command. "
        "Folder name should starts from root, "
        f'nested folders should be separated with "{to_code("/")}" character.'
        "\n\n"
        "<b>Yandex.Disk Access</b>"
        "\n"
        f"{CommandsNames.YD_AUTH.value} — grant me access to your Yandex.Disk"
        "\n"
        f"{CommandsNames.YD_REVOKE.value} — revoke my access to your Yandex.Disk"
        "\n\n"
        "<b>Settings</b>"
        "\n"
        f"{CommandsNames.SETTINGS.value} — edit your settings"
        "\n\n"
        "<b>Information</b>"
        "\n"
        f"{CommandsNames.ABOUT.value} — read about me")

    telegram.send_message(chat_id=g.telegram_chat.id,
                          parse_mode="HTML",
                          text=text)
 def send_html_message(self, chat_id: int, html_text: str) -> None:
     """
     Sends HTML message to Telegram user.
     """
     telegram.send_message(chat_id=chat_id,
                           text=html_text,
                           parse_mode="HTML",
                           disable_web_page_preview=True)
def handle():
    """
    Handles unknown command.
    """
    telegram.send_message(
        chat_id=g.telegram_chat.id,
        text=("I don't know this command. "
              f"See commands list or type {CommandsNames.HELP.value}"))
def handle(*args, **kwargs):
    """
    Handles `/commands` command.
    """
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    text = create_commands_list_html_text()

    telegram.send_message(chat_id=chat_id, text=text, parse_mode="HTML")
Example #10
0
def message_grant_access_code(chat_id: int, url: str,
                              lifetime_in_seconds: int) -> None:
    open_link_button_text = "Grant access"
    revoke_command = CommandName.YD_REVOKE.value
    yandex_passport_url = "https://passport.yandex.ru/profile"
    source_code_url = current_app.config["PROJECT_URL_FOR_CODE"]
    privacy_policy_url = absolute_url_for("legal.privacy_policy")
    terms_url = absolute_url_for("legal.terms_and_conditions")
    lifetime_in_minutes = int(lifetime_in_seconds / 60)

    telegram.send_message(
        chat_id=chat_id,
        parse_mode="HTML",
        disable_web_page_preview=True,
        text=(f'Open special link by pressing on "{open_link_button_text}" '
              "button and grant me access to your Yandex.Disk."
              "\n\n"
              "<b>IMPORTANT: don't give this link to anyone, "
              "because it contains your secret information.</b>"
              "\n\n"
              f"<i>This link will expire in {lifetime_in_minutes} minutes.</i>"
              "\n"
              "<i>This link leads to Yandex page. After granting access, "
              "you will need to send me the issued code.</i>"
              "\n\n"
              "<b>It is safe to give the access?</b>"
              "\n"
              "Yes! I'm getting access only to your Yandex.Disk, "
              "not to your account. You can revoke my access at any time "
              f"with {revoke_command} or in your "
              f'<a href="{yandex_passport_url}">Yandex profile</a>. '
              "By the way, i'm "
              f'<a href="{source_code_url}">open-source</a> '
              "and you can make sure that your data will be safe. "
              "You can even create your own bot with my functionality "
              "if using me makes you feel uncomfortable (:"
              "\n\n"
              "By using me, you accept "
              f'<a href="{privacy_policy_url}">Privacy Policy</a> and '
              f'<a href="{terms_url}">Terms of service</a>. '),
        reply_markup={
            "inline_keyboard": [[{
                "text": open_link_button_text,
                "url": url
            }]]
        })
    # TODO:
    # React on press of inline keyboard button
    # (https://core.telegram.org/bots/api#callbackquery),
    # not send separate message immediately.
    # But it requires refactoring of dispatcher and others.
    # At the moment let it be implemented as it is,
    # because "Console Client" is mostly for developers, not users.
    telegram.send_message(chat_id=chat_id,
                          text=("Open this link, grant me an access "
                                "and then send me a code"))
def handle(*args, **kwargs):
    """
    Handles `/help` command.
    """
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    text = create_help_html_text()
    telegram.send_message(chat_id=chat_id,
                          parse_mode="HTML",
                          text=text,
                          disable_web_page_preview=True)
def request_private_chat(chat_telegram_id: int) -> None:
    """
    Aborts command execution due to lack of private chat with user.
    """
    telegram.send_message(
        chat_id=chat_telegram_id,
        text=("I need to send you your secret information, "
              "but i don't know any private chat with you. "
              "First, contact me through private chat (direct message). "
              "After that repeat your request."))
Example #13
0
def end_console_client(db_user, chat_id: int, code: str) -> None:
    state = get_user_chat_data(db_user.telegram_id, chat_id,
                               "yandex_oauth_console_client_state")
    delete_user_chat_data(db_user.telegram_id, chat_id,
                          "yandex_oauth_console_client_state")

    if not state:
        return telegram.send_message(
            chat_id=chat_id,
            text=("You waited too long. "
                  f"Send {CommandName.YD_AUTH} again."))

    client = yandex_oauth.YandexOAuthConsoleClient()
    result = None
    message = None

    try:
        result = client.handle_code(state, code)
    except yandex_oauth.InvalidState:
        message = ("Your credentials is not valid. "
                   f"Try send {CommandName.YD_AUTH} again.")
    except yandex_oauth.ExpiredInsertToken:
        message = ("You waited too long. "
                   f"Send {CommandName.YD_AUTH} again.")
    except yandex_oauth.InvalidInsertToken:
        message = ("You have too many authorization sessions. "
                   f"Send {CommandName.YD_AUTH} again and use only last link.")
    except Exception as error:
        cancel_command(chat_id)
        raise error

    if message:
        return telegram.send_message(chat_id=chat_id, text=message)

    if not result["ok"]:
        error = result["error"]
        title = error.get("title", "Unknown error")
        description = error.get("description", "Can't tell you more")

        return telegram.send_message(
            chat_id=chat_id,
            parse_mode="HTML",
            text=("<b>Yandex.OAuth Error</b>"
                  "\n\n"
                  f"<b>Error</b>: {title}"
                  "\n"
                  f"<b>Description</b>: {description}"
                  "\n\n"
                  "I still don't have access. "
                  f"Start new session using {CommandName.YD_AUTH.value}"))

    message_access_token_granted(chat_id)
def handle():
    """
    Handles `/yandex_disk_revoke` command.

    Revokes bot access to user Yandex.Disk.
    """
    user = g.db_user
    incoming_chat = g.db_chat
    private_chat = g.db_private_chat

    if (private_chat is None):
        return request_private_chat(incoming_chat.telegram_id)

    if (
        (user is None) or
        (user.yandex_disk_token is None) or
        (not user.yandex_disk_token.have_access_token())
    ):
        telegram.send_message(
            chat_id=private_chat.telegram_id,
            text=(
                "You don't granted me access to your Yandex.Disk."
                "\n"
                f"You can do that with {CommandsNames.YD_AUTH.value}"
            )
        )

        return

    user.yandex_disk_token.clear_all_tokens()
    db.session.commit()

    current_datetime = get_current_datetime()
    date = current_datetime["date"]
    time = current_datetime["time"]
    timezone = current_datetime["timezone"]

    telegram.send_message(
        chat_id=private_chat.telegram_id,
        parse_mode="HTML",
        disable_web_page_preview=True,
        text=(
            "<b>Access to Yandex.Disk Revoked</b>"
            "\n\n"
            "You successfully revoked my access to your Yandex.Disk "
            f"on {date} at {time} {timezone}."
            "\n\n"
            "Don't forget to do that in your "
            '<a href="https://passport.yandex.ru/profile">Yandex Profile</a>.'
        )
    )
def handle(*args, **kwargs):
    """
    Handles unknown command.
    """
    telegram.send_message(
        chat_id=kwargs.get(
            "chat_id",
            g.telegram_chat.id
        ),
        text=(
            "I don't know this command. "
            f"See commands list or type {CommandName.HELP.value}"
        )
    )
Example #16
0
def handle_success():
    """
    Handles success user authorization.
    """
    state = request.args["state"]
    code = request.args["code"]
    client = yandex_oauth.YandexOAuthAutoCodeClient()
    result = None

    try:
        result = client.after_success_redirect(state, code)
    except yandex_oauth.InvalidState:
        return create_error_response("invalid_credentials")
    except yandex_oauth.ExpiredInsertToken:
        return create_error_response("link_expired")
    except yandex_oauth.InvalidInsertToken:
        return create_error_response("invalid_insert_token")
    except Exception as error:
        print(error)
        return create_error_response("internal_server_error")

    if not result["ok"]:
        return create_error_response(
            error_code="internal_server_error",
            raw_error_title=result["error"],
            raw_error_description=result.get("error_description"))

    user = result["user"]
    private_chat = ChatQuery.get_private_chat(user.id)

    if private_chat:
        now = get_current_datetime()
        date = now["date"]
        time = now["time"]
        timezone = now["timezone"]

        telegram.send_message(
            chat_id=private_chat.telegram_id,
            parse_mode="HTML",
            text=("<b>Access to Yandex.Disk Granted</b>"
                  "\n\n"
                  "My access was attached to your Telegram account "
                  f"on {date} at {time} {timezone}."
                  "\n\n"
                  "If it wasn't you, then detach this access with "
                  f"{CommandName.YD_REVOKE.value}"))

    return create_success_response()
Example #17
0
def message_access_token_refreshed(chat_id: int) -> None:
    now = get_current_datetime()
    date = now["date"]
    time = now["time"]
    timezone = now["timezone"]

    telegram.send_message(
        chat_id=chat_id,
        parse_mode="HTML",
        text=("<b>Access to Yandex.Disk Refreshed</b>"
              "\n\n"
              "Your granted access was refreshed automatically by me "
              f"on {date} at {time} {timezone}."
              "\n\n"
              "If it wasn't you, you can detach this access with "
              f"{CommandName.YD_REVOKE.value}"))
def request_absolute_folder_name(chat_telegram_id: int) -> None:
    """
    Sends a message that asks a user to send an
    absolute path of folder.
    """
    telegram.send_message(
        chat_id=chat_telegram_id,
        parse_mode="HTML",
        text=("Send a folder name."
              "\n\n"
              "It should starts from root directory, "
              "nested folders should be separated with "
              '"<code>/</code>" character. '
              "In short, i expect a full path."
              "\n\n"
              "Example: <code>Telegram Bot/kittens and raccoons</code>"))
Example #19
0
def message_access_token_granted(chat_id: int) -> None:
    now = get_current_datetime()
    date = now["date"]
    time = now["time"]
    timezone = now["timezone"]

    telegram.send_message(
        chat_id=chat_id,
        parse_mode="HTML",
        text=("<b>Access to Yandex.Disk Granted</b>"
              "\n\n"
              "My access was attached to your Telegram account "
              f"on {date} at {time} {timezone}."
              "\n\n"
              "If it wasn't you, then detach this access with "
              f"{CommandName.YD_REVOKE.value}"))
def handle(*args, **kwargs):
    message = kwargs.get("message", g.telegram_message)
    user_id = kwargs.get("user_id", g.telegram_user.id)
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    folder_name = extract_absolute_path(message,
                                        CommandName.CREATE_FOLDER.value,
                                        kwargs.get("route_source"))

    if not folder_name:
        if stateful_chat_is_enabled():
            set_disposable_handler(
                user_id, chat_id, CommandName.CREATE_FOLDER.value, [
                    DispatcherEvent.PLAIN_TEXT.value,
                    DispatcherEvent.BOT_COMMAND.value,
                    DispatcherEvent.EMAIL.value, DispatcherEvent.HASHTAG.value,
                    DispatcherEvent.URL.value
                ], current_app.config["RUNTIME_DISPOSABLE_HANDLER_EXPIRE"])

        return request_absolute_folder_name(chat_id)

    user = g.db_user
    access_token = user.yandex_disk_token.get_access_token()
    last_status_code = None

    try:
        last_status_code = create_folder(user_access_token=access_token,
                                         folder_name=folder_name)
    except YandexAPIRequestError as error:
        cancel_command(chat_id)
        raise error
    except YandexAPICreateFolderError as error:
        send_yandex_disk_error(chat_id, str(error))

        # it is expected error and should be
        # logged only to user
        return

    text = None

    if (last_status_code == 201):
        text = "Created"
    elif (last_status_code == 409):
        text = "Already exists"
    else:
        text = f"Unknown operation status: {last_status_code}"

    telegram.send_message(chat_id=chat_id, text=text)
Example #21
0
def message_grant_access_redirect(chat_id: int, url: str,
                                  lifetime_in_seconds: int) -> None:
    open_link_button_text = "Grant access"
    revoke_command = CommandName.YD_REVOKE.value
    yandex_passport_url = "https://passport.yandex.ru/profile"
    source_code_url = current_app.config["PROJECT_URL_FOR_CODE"]
    privacy_policy_url = absolute_url_for("legal.privacy_policy")
    terms_url = absolute_url_for("legal.terms_and_conditions")
    lifetime_in_minutes = int(lifetime_in_seconds / 60)

    telegram.send_message(
        chat_id=chat_id,
        parse_mode="HTML",
        disable_web_page_preview=True,
        text=(
            f'Open special link by pressing on "{open_link_button_text}" '
            "button and grant me access to your Yandex.Disk."
            "\n\n"
            "<b>IMPORTANT: don't give this link to anyone, "
            "because it contains your secret information.</b>"
            "\n\n"
            f"<i>This link will expire in {lifetime_in_minutes} minutes.</i>"
            "\n"
            "<i>This link leads to Yandex page and redirects to bot page.</i>"
            "\n\n"
            "<b>It is safe to give the access?</b>"
            "\n"
            "Yes! I'm getting access only to your Yandex.Disk, "
            "not to your account. You can revoke my access at any time "
            f"with {revoke_command} or in your "
            f'<a href="{yandex_passport_url}">Yandex profile</a>. '
            "By the way, i'm "
            f'<a href="{source_code_url}">open-source</a> '
            "and you can make sure that your data will be safe. "
            "You can even create your own bot with my functionality "
            "if using me makes you feel uncomfortable (:"
            "\n\n"
            "By using me, you accept "
            f'<a href="{privacy_policy_url}">Privacy Policy</a> and '
            f'<a href="{terms_url}">Terms of service</a>. '),
        reply_markup={
            "inline_keyboard": [[{
                "text": open_link_button_text,
                "url": url
            }]]
        })
def message_access_token_removed(chat_id: int) -> None:
    now = get_current_datetime()
    date = now["date"]
    time = now["time"]
    timezone = now["timezone"]

    telegram.send_message(
        chat_id=chat_id,
        parse_mode="HTML",
        disable_web_page_preview=True,
        text=(
            "<b>Access to Yandex.Disk Revoked</b>"
            "\n\n"
            "You successfully revoked my access to your Yandex.Disk "
            f"on {date} at {time} {timezone}."
            "\n\n"
            "Don't forget to do that in your "
            '<a href="https://passport.yandex.ru/profile">Yandex Profile</a>.'
            "\n"
            f"To grant access again use {CommandName.YD_AUTH.value}"))
def handle(*args, **kwargs):
    """
    Handles `/disk_info` command.
    """
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    user = g.db_user
    access_token = user.yandex_disk_token.get_access_token()
    info = None

    try:
        info = get_disk_info(access_token)
    except YandexAPIRequestError as error:
        cancel_command(chat_id)
        raise error

    text = create_disk_info_html_text(info)

    telegram.send_message(chat_id=chat_id,
                          text=text,
                          parse_mode="HTML",
                          disable_web_page_preview=True)
def handle():
    """
    Handles `/create_folder` command.
    """
    message = g.telegram_message
    user = g.db_user
    chat = g.db_chat
    message_text = message.get_text()
    folder_name = message_text.replace(CommandsNames.CREATE_FOLDER.value,
                                       "").strip()

    if not (folder_name):
        return abort_command(chat.telegram_id)

    access_token = user.yandex_disk_token.get_access_token()
    last_status_code = None

    try:
        last_status_code = create_folder(user_access_token=access_token,
                                         folder_name=folder_name)
    except YandexAPIRequestError as error:
        print(error)
        return cancel_command(chat.telegram_id)
    except YandexAPICreateFolderError as error:
        error_text = (str(error) or "Unknown Yandex.Disk error")

        telegram.send_message(chat_id=chat.telegram_id, text=error_text)

        return

    text = None

    if (last_status_code == 201):
        text = "Created"
    elif (last_status_code == 409):
        text = "Already exists"
    else:
        text = f"Unknown status code: {last_status_code}"

    telegram.send_message(chat_id=chat.telegram_id, text=text)
def handle(*args, **kwargs):
    """
    Handles `/unpublish` command.
    """
    message = kwargs.get("message", g.telegram_message)
    user_id = kwargs.get("user_id", g.telegram_user.id)
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    path = extract_absolute_path(message, CommandName.UNPUBLISH.value,
                                 kwargs.get("route_source"))

    if not path:
        if stateful_chat_is_enabled():
            set_disposable_handler(
                user_id, chat_id, CommandName.UNPUBLISH.value, [
                    DispatcherEvent.PLAIN_TEXT.value,
                    DispatcherEvent.BOT_COMMAND.value,
                    DispatcherEvent.EMAIL.value, DispatcherEvent.HASHTAG.value,
                    DispatcherEvent.URL.value
                ], current_app.config["RUNTIME_DISPOSABLE_HANDLER_EXPIRE"])

        return request_absolute_path(chat_id)

    user = g.db_user
    access_token = user.yandex_disk_token.get_access_token()

    try:
        unpublish_item(access_token, path)
    except YandexAPIRequestError as error:
        cancel_command(chat_id)
        raise error
    except YandexAPIUnpublishItemError as error:
        send_yandex_disk_error(chat_id, str(error))

        # it is expected error and should be
        # logged only to user
        return

    telegram.send_message(chat_id=chat_id, text="Unpublished")
def abort_command(chat_telegram_id: int,
                  reason: AbortReason,
                  edit_message: int = None,
                  reply_to_message: int = None) -> None:
    """
    Aborts command execution due to invalid message data.

    - don't confuse with `cancel_command()`.
    - if `edit_message` Telegram ID specified, then
    that message will be edited.
    - if `reply_to_message` Telegram ID specified, then
    that message will be used for reply message.
    """
    texts = {
        AbortReason.UNKNOWN:
        ("I can't handle this because something is wrong."),
        AbortReason.NO_SUITABLE_DATA: ("I can't handle this because "
                                       "you didn't send any suitable data "
                                       "for that command."),
        AbortReason.EXCEED_FILE_SIZE_LIMIT: (
            "I can't handle file of such a large size. "
            "At the moment my limit is "
            f"{current_app.config['TELEGRAM_API_MAX_FILE_SIZE'] / 1024 / 1024} MB."  # noqa
        )
    }
    text = texts[reason]

    if (edit_message is not None):
        telegram.edit_message_text(chat_id=chat_telegram_id,
                                   message_id=edit_message,
                                   text=text)
    elif (reply_to_message is not None):
        telegram.send_message(chat_id=chat_telegram_id,
                              reply_to_message_id=reply_to_message,
                              text=text)
    else:
        telegram.send_message(chat_id=chat_telegram_id, text=text)
def abort_command(
    chat_telegram_id: int,
    edit_message: int = None,
    reply_to_message: int = None
) -> None:
    """
    Aborts command execution due to invalid message data.

    - don't confuse with `cancel_command()`.
    - if `edit_message` Telegram ID specified, then
    that message will be edited.
    - if `reply_to_message` Telegram ID specified, then
    that message will be used for reply message.
    """
    text = (
        "I can't handle this because "
        "you didn't send any suitable data "
        "for that command."
    )

    if (edit_message is not None):
        telegram.edit_message_text(
            chat_id=chat_telegram_id,
            message_id=edit_message,
            text=text
        )
    elif (reply_to_message is not None):
        telegram.send_message(
            chat_id=chat_telegram_id,
            reply_to_message_id=reply_to_message,
            text=text
        )
    else:
        telegram.send_message(
            chat_id=chat_telegram_id,
            text=text
        )
def send_yandex_disk_error(chat_telegram_id: int,
                           error_text: str,
                           reply_to_message_id: int = None) -> None:
    """
    Sends a message that indicates that Yandex.Disk threw an error.

    :param error_text:
    Text of error that will be printed.
    Can be empty.
    :param reply_to_message_id:
    If specified, then sended message will be a reply message.
    """
    kwargs = {
        "chat_id": chat_telegram_id,
        "parse_mode": "HTML",
        "text": ("<b>Yandex.Disk Error</b>"
                 "\n\n"
                 f"{error_text or 'Unknown'}")
    }

    if reply_to_message_id is not None:
        kwargs["reply_to_message_id"] = reply_to_message_id

    telegram.send_message(**kwargs)
def handle(*args, **kwargs):
    """
    Handles `/space_info` command.
    """
    user = g.db_user
    chat_id = kwargs.get("chat_id", g.telegram_chat.id)
    access_token = user.yandex_disk_token.get_access_token()
    disk_info = None

    # If all task queue workers are busy,
    # it can take a long time before they
    # execute `send_photo()` function.
    # We will indicate to user that everything
    # is fine and result will be sent soon
    sended_message = telegram.send_message(chat_id=chat_id,
                                           text="Generating...")
    sended_message_id = sended_message["content"]["message_id"]

    try:
        disk_info = get_disk_info(access_token)
    except YandexAPIRequestError as error:
        cancel_command(chat_telegram_id=chat_id,
                       edit_message=sended_message_id)

        raise error

    current_utc_date = get_current_utc_datetime()
    current_iso_date = get_current_iso_datetime()
    jpeg_image = create_space_chart(total_space=disk_info["total_space"],
                                    used_space=disk_info["used_space"],
                                    trash_size=disk_info["trash_size"],
                                    caption=current_utc_date)
    filename = f"{to_filename(current_iso_date)}.jpg"
    file_caption = f"Yandex.Disk space at {current_utc_date}"
    arguments = (jpeg_image, filename, file_caption, chat_id,
                 sended_message_id)

    if task_queue.is_enabled:
        job_timeout = current_app.config["RUNTIME_SPACE_INFO_WORKER_TIMEOUT"]

        task_queue.enqueue(send_photo,
                           args=arguments,
                           description=CommandName.SPACE_INFO.value,
                           job_timeout=job_timeout,
                           result_ttl=0,
                           failure_ttl=0)
    else:
        send_photo(*arguments)
    def reply_to_message(self,
                         incoming_message_id: int,
                         chat_id: int,
                         text: str,
                         html_text=False) -> None:
        """
        Sends reply message to Telegram user.

        - if message already was sent, then sent
        message will be updated with new text.
        - NOTE: using HTML text may lead to error,
        because text should be compared with already
        sended text, but already sended text will not
        contain HTML tags (even if they was before sending),
        and `text` will, so, comparing already sended HTML
        text and `text` always will results to `False`.
        """
        enabled_html = {}

        if html_text:
            enabled_html["parse_mode"] = "HTML"

        result = None

        if self.sended_message is None:
            result = telegram.send_message(
                reply_to_message_id=incoming_message_id,
                chat_id=chat_id,
                text=text,
                allow_sending_without_reply=True,
                disable_web_page_preview=True,
                **enabled_html)
        elif (text != self.sended_message.get_text()):
            result = telegram.edit_message_text(
                message_id=self.sended_message.message_id,
                chat_id=chat_id,
                text=text,
                disable_web_page_preview=True,
                **enabled_html)

        new_message_sended = ((result is not None) and result["ok"])

        if new_message_sended:
            self.sended_message = TelegramMessage(result["content"])