python-telegram-bot/telegram/helpers.py

168 lines
5.7 KiB
Python

#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains convenience helper functions.
.. versionchanged:: 14.0
Previously, the contents of this module were available through the (no longer existing)
module ``telegram.utils.helpers``.
"""
import re
from html import escape
from typing import (
TYPE_CHECKING,
Optional,
Union,
)
if TYPE_CHECKING:
from telegram import Message, Update
def escape_markdown(text: str, version: int = 1, entity_type: str = None) -> str:
"""Helper function to escape telegram markup symbols.
Args:
text (:obj:`str`): The text.
version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown.
Either ``1`` or ``2``. Defaults to ``1``.
entity_type (:obj:`str`, optional): For the entity types ``PRE``, ``CODE`` and the link
part of ``TEXT_LINKS``, only certain characters need to be escaped in ``MarkdownV2``.
See the official API documentation for details. Only valid in combination with
``version=2``, will be ignored else.
"""
if int(version) == 1:
escape_chars = r'_*`['
elif int(version) == 2:
if entity_type in ['pre', 'code']:
escape_chars = r'\`'
elif entity_type == 'text_link':
escape_chars = r'\)'
else:
escape_chars = r'_*[]()~`>#+-=|{}.!'
else:
raise ValueError('Markdown version must be either 1 or 2!')
return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text)
def mention_html(user_id: Union[int, str], name: str) -> str:
"""
Args:
user_id (:obj:`int`): The user's id which you want to mention.
name (:obj:`str`): The name the mention is showing.
Returns:
:obj:`str`: The inline mention for the user as HTML.
"""
return f'<a href="tg://user?id={user_id}">{escape(name)}</a>'
def mention_markdown(user_id: Union[int, str], name: str, version: int = 1) -> str:
"""
Args:
user_id (:obj:`int`): The user's id which you want to mention.
name (:obj:`str`): The name the mention is showing.
version (:obj:`int` | :obj:`str`): Use to specify the version of Telegram's Markdown.
Either ``1`` or ``2``. Defaults to ``1``.
Returns:
:obj:`str`: The inline mention for the user as Markdown.
"""
return f'[{escape_markdown(name, version=version)}](tg://user?id={user_id})'
def effective_message_type(entity: Union['Message', 'Update']) -> Optional[str]:
"""
Extracts the type of message as a string identifier from a :class:`telegram.Message` or a
:class:`telegram.Update`.
Args:
entity (:class:`telegram.Update` | :class:`telegram.Message`): The ``update`` or
``message`` to extract from.
Returns:
:obj:`str`: One of ``Message.MESSAGE_TYPES``
"""
# Importing on file-level yields cyclic Import Errors
from telegram import Message, Update # pylint: disable=import-outside-toplevel
if isinstance(entity, Message):
message = entity
elif isinstance(entity, Update):
message = entity.effective_message # type: ignore[assignment]
else:
raise TypeError(f"entity is not Message or Update (got: {type(entity)})")
for i in Message.MESSAGE_TYPES:
if getattr(message, i, None):
return i
return None
def create_deep_linked_url(bot_username: str, payload: str = None, group: bool = False) -> str:
"""
Creates a deep-linked URL for this ``bot_username`` with the specified ``payload``.
See https://core.telegram.org/bots#deep-linking to learn more.
The ``payload`` may consist of the following characters: ``A-Z, a-z, 0-9, _, -``
Note:
Works well in conjunction with
``CommandHandler("start", callback, filters = Filters.regex('payload'))``
Examples:
``create_deep_linked_url(bot.get_me().username, "some-params")``
Args:
bot_username (:obj:`str`): The username to link to
payload (:obj:`str`, optional): Parameters to encode in the created URL
group (:obj:`bool`, optional): If :obj:`True` the user is prompted to select a group to
add the bot to. If :obj:`False`, opens a one-on-one conversation with the bot.
Defaults to :obj:`False`.
Returns:
:obj:`str`: An URL to start the bot with specific parameters
"""
if bot_username is None or len(bot_username) <= 3:
raise ValueError("You must provide a valid bot_username.")
base_url = f'https://t.me/{bot_username}'
if not payload:
return base_url
if len(payload) > 64:
raise ValueError("The deep-linking payload must not exceed 64 characters.")
if not re.match(r'^[A-Za-z0-9_-]+$', payload):
raise ValueError(
"Only the following characters are allowed for deep-linked "
"URLs: A-Z, a-z, 0-9, _ and -"
)
if group:
key = 'startgroup'
else:
key = 'start'
return f'{base_url}?{key}={payload}'