nextgram 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextgram/__init__.py +34 -0
- nextgram/aiogram_rich.py +123 -0
- nextgram/http_rich.py +190 -0
- nextgram/ptb_rich.py +137 -0
- nextgram/rich_ui.py +1873 -0
- nextgram/telethon.py +488 -0
- nextgram/widgets.py +726 -0
- nextgram-0.9.0.dist-info/METADATA +908 -0
- nextgram-0.9.0.dist-info/RECORD +12 -0
- nextgram-0.9.0.dist-info/WHEEL +5 -0
- nextgram-0.9.0.dist-info/licenses/LICENSE +21 -0
- nextgram-0.9.0.dist-info/top_level.txt +1 -0
nextgram/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""nextgram: Telegram Bot API 10.3 rich messages and Bot API 10.4 HTML
|
|
2
|
+
widgets, across six frameworks.
|
|
3
|
+
|
|
4
|
+
Pyrogram, Pyrofork and Kurigram are one family (three distributions of
|
|
5
|
+
the same ``pyrogram`` package name) and get full native support here, in
|
|
6
|
+
``nextgram.rich_ui`` — importing ``nextgram`` itself needs none of them
|
|
7
|
+
installed. Telethon, aiogram and python-telegram-bot are each supported
|
|
8
|
+
through their own optional module, imported explicitly so a bot using
|
|
9
|
+
only one framework never has to install the others:
|
|
10
|
+
|
|
11
|
+
- ``nextgram.telethon`` — ``from nextgram import telethon``
|
|
12
|
+
- ``nextgram.aiogram_rich`` — ``from nextgram import aiogram_rich``
|
|
13
|
+
- ``nextgram.ptb_rich`` — ``from nextgram import ptb_rich``
|
|
14
|
+
|
|
15
|
+
``nextgram.widgets`` (Bot API 10.4 widgets) is framework-agnostic by
|
|
16
|
+
construction — it only ever needs a bot token — so every function in it
|
|
17
|
+
already works the same from all six frameworks without a separate module
|
|
18
|
+
per framework; see its own docstring.
|
|
19
|
+
|
|
20
|
+
The content builders (``rich_heading``, ``rich_list``, ``rich_button``,
|
|
21
|
+
``rich_table``, ...) are plain string functions with no framework
|
|
22
|
+
dependency at all — the same HTML works everywhere; only how it gets
|
|
23
|
+
*sent* differs per framework, which is what the modules above are for.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from . import rich_ui
|
|
27
|
+
from .rich_ui import *
|
|
28
|
+
from .rich_ui import __all__ as _rich_all
|
|
29
|
+
from . import widgets
|
|
30
|
+
from .widgets import *
|
|
31
|
+
from .widgets import __all__ as _widgets_all
|
|
32
|
+
|
|
33
|
+
__version__ = "0.9.0"
|
|
34
|
+
__all__ = list(_rich_all) + list(_widgets_all) + ["rich_ui", "widgets", "__version__"]
|
nextgram/aiogram_rich.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""nextgram.aiogram_rich: rich messages and widgets for aiogram bots.
|
|
2
|
+
|
|
3
|
+
aiogram is optional — importing ``nextgram`` never requires it, and this
|
|
4
|
+
module has no import-time dependency on the ``aiogram`` package itself
|
|
5
|
+
(it only reads a few attributes off whatever ``Bot``/``Message`` objects
|
|
6
|
+
are passed in). Rich-message sending goes over the plain Bot HTTP API via
|
|
7
|
+
``nextgram.http_rich`` (see that module's docstring for exactly what is
|
|
8
|
+
confirmed vs. speculative); widgets go over the same HTTP API via
|
|
9
|
+
``nextgram.widgets``, which already works with any client exposing a
|
|
10
|
+
``.token`` attribute — aiogram's ``Bot`` does.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from .http_rich import coerce_markup_to_dict, edit_rich_html_via_http, send_rich_html_via_http
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("nextgram.aiogram_rich")
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"aiogram_bot_token",
|
|
22
|
+
"aiogram_rich_send",
|
|
23
|
+
"aiogram_rich_reply",
|
|
24
|
+
"aiogram_rich_edit",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def aiogram_bot_token(bot) -> str:
|
|
29
|
+
"""Pull the bot token off an aiogram ``Bot`` instance."""
|
|
30
|
+
token = getattr(bot, "token", None)
|
|
31
|
+
if not token:
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
"aiogram_bot_token needs a Bot instance with a .token attribute"
|
|
34
|
+
)
|
|
35
|
+
return token
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def aiogram_rich_send(
|
|
39
|
+
bot,
|
|
40
|
+
chat_id,
|
|
41
|
+
html_text: str,
|
|
42
|
+
*,
|
|
43
|
+
markdown: bool = False,
|
|
44
|
+
reply_markup=None,
|
|
45
|
+
reply_to_message_id=None,
|
|
46
|
+
message_thread_id=None,
|
|
47
|
+
disable_notification=None,
|
|
48
|
+
protect_content=None,
|
|
49
|
+
):
|
|
50
|
+
"""Send rich HTML/Markdown to ``chat_id`` with an aiogram ``Bot``.
|
|
51
|
+
|
|
52
|
+
``reply_markup`` accepts an aiogram ``InlineKeyboardMarkup`` (or any
|
|
53
|
+
object with ``.model_dump``/``.to_dict``) as well as a plain dict.
|
|
54
|
+
Returns the Bot API ``result`` dict (not an aiogram ``Message``) —
|
|
55
|
+
use ``result["message_id"]`` for the sent message's id.
|
|
56
|
+
"""
|
|
57
|
+
token = aiogram_bot_token(bot)
|
|
58
|
+
reply_parameters = (
|
|
59
|
+
{"message_id": reply_to_message_id} if reply_to_message_id else None
|
|
60
|
+
)
|
|
61
|
+
return await send_rich_html_via_http(
|
|
62
|
+
token,
|
|
63
|
+
chat_id,
|
|
64
|
+
html_text,
|
|
65
|
+
markdown=markdown,
|
|
66
|
+
reply_markup=coerce_markup_to_dict(reply_markup),
|
|
67
|
+
reply_parameters=reply_parameters,
|
|
68
|
+
message_thread_id=message_thread_id,
|
|
69
|
+
disable_notification=disable_notification,
|
|
70
|
+
protect_content=protect_content,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def aiogram_rich_reply(message, html_text: str, *, bot=None, **kwargs):
|
|
75
|
+
"""Reply to an aiogram ``Message`` with rich HTML/Markdown.
|
|
76
|
+
|
|
77
|
+
``bot`` defaults to ``message.bot`` (aiogram sets this on every
|
|
78
|
+
incoming message/update automatically).
|
|
79
|
+
"""
|
|
80
|
+
app = bot or getattr(message, "bot", None)
|
|
81
|
+
chat = getattr(getattr(message, "chat", None), "id", None)
|
|
82
|
+
if app is None or chat is None:
|
|
83
|
+
logger.debug("[aiogram_rich_reply] missing bot/chat on message")
|
|
84
|
+
return None
|
|
85
|
+
return await aiogram_rich_send(
|
|
86
|
+
app, chat, html_text, reply_to_message_id=getattr(message, "message_id", None), **kwargs
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def aiogram_rich_edit(
|
|
91
|
+
message,
|
|
92
|
+
html_text: str,
|
|
93
|
+
*,
|
|
94
|
+
bot=None,
|
|
95
|
+
chat_id=None,
|
|
96
|
+
message_id=None,
|
|
97
|
+
markdown: bool = False,
|
|
98
|
+
reply_markup=None,
|
|
99
|
+
):
|
|
100
|
+
"""Edit a message to rich HTML/Markdown with an aiogram ``Bot``.
|
|
101
|
+
|
|
102
|
+
``message`` is an aiogram ``Message`` (``.chat.id``/``.message_id``
|
|
103
|
+
read automatically) or ``None``, in which case ``chat_id`` and
|
|
104
|
+
``message_id`` are required.
|
|
105
|
+
"""
|
|
106
|
+
if message is not None:
|
|
107
|
+
app = bot or getattr(message, "bot", None)
|
|
108
|
+
chat_id = getattr(getattr(message, "chat", None), "id", None)
|
|
109
|
+
message_id = getattr(message, "message_id", None)
|
|
110
|
+
else:
|
|
111
|
+
app = bot
|
|
112
|
+
if app is None or chat_id is None or message_id is None:
|
|
113
|
+
logger.debug("[aiogram_rich_edit] missing bot/chat_id/message_id")
|
|
114
|
+
return None
|
|
115
|
+
token = aiogram_bot_token(app)
|
|
116
|
+
return await edit_rich_html_via_http(
|
|
117
|
+
token,
|
|
118
|
+
chat_id,
|
|
119
|
+
message_id,
|
|
120
|
+
html_text,
|
|
121
|
+
markdown=markdown,
|
|
122
|
+
reply_markup=coerce_markup_to_dict(reply_markup),
|
|
123
|
+
)
|
nextgram/http_rich.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""nextgram.http_rich: raw Bot HTTP API rich-message sending.
|
|
2
|
+
|
|
3
|
+
aiogram and python-telegram-bot have no MTProto layer at all — just a bot
|
|
4
|
+
token and the plain Bot HTTP API — so ``nextgram.aiogram_rich`` and
|
|
5
|
+
``nextgram.ptb_rich`` both build on this module instead of
|
|
6
|
+
``nextgram.rich_ui``'s Pyrogram-native path. Sending calls the same
|
|
7
|
+
``sendRichMessage`` HTTP endpoint ``nextgram.rich_ui._send_blocks_via_http``
|
|
8
|
+
already uses for typed blocks, this time with a plain ``{"html": ...}``
|
|
9
|
+
payload (the HTTP shape of ``InputRichMessage(html=...)``) instead of
|
|
10
|
+
``blocks``. This is a real, already-used endpoint, not a new guess.
|
|
11
|
+
|
|
12
|
+
Editing is less certain: there is no documented standalone
|
|
13
|
+
``editRichMessage`` HTTP method, only Pyrogram's native, MTProto-level
|
|
14
|
+
``edit_message_text(..., rich_message=...)``. :func:`edit_rich_html_via_http`
|
|
15
|
+
tries the same ``rich_message`` field on the plain HTTP ``editMessageText``
|
|
16
|
+
call — speculative, since it has never been confirmed against a live bot —
|
|
17
|
+
and falls back to a plain-HTML edit on any failure, exactly as the send
|
|
18
|
+
side falls back when ``sendRichMessage`` is rejected or unavailable.
|
|
19
|
+
|
|
20
|
+
Buttons built with ``rich_button()``/``rich_button_row()`` are embedded
|
|
21
|
+
*inside* the rich HTML and only become clickable when the rich payload is
|
|
22
|
+
actually accepted and rendered that way by the Telegram client — if a send
|
|
23
|
+
or edit falls back to plain HTML, those buttons degrade to plain text
|
|
24
|
+
labels (via ``rich_ui._plain_fallback``), exactly as they do on the
|
|
25
|
+
Pyrogram path when ``RICH_AVAILABLE`` is ``False``.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
|
|
31
|
+
from .rich_ui import _has_rich_only_tags, _normalize_html, _only_set, _plain_fallback
|
|
32
|
+
from .widgets import WidgetAPIError, _call_bot_api
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("nextgram.http_rich")
|
|
35
|
+
|
|
36
|
+
__all__ = ["send_rich_html_via_http", "edit_rich_html_via_http", "coerce_markup_to_dict"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def coerce_markup_to_dict(reply_markup):
|
|
40
|
+
"""Turn a framework-native keyboard object into a plain JSON-able dict.
|
|
41
|
+
|
|
42
|
+
``None``/dict pass through unchanged. aiogram v3's Pydantic models use
|
|
43
|
+
``.model_dump(mode="json", exclude_none=True)``; python-telegram-bot's
|
|
44
|
+
use ``.to_dict()``; both are tried, in that order, before giving up and
|
|
45
|
+
returning the object as-is (letting the eventual JSON encode fail
|
|
46
|
+
loudly rather than silently dropping the keyboard).
|
|
47
|
+
"""
|
|
48
|
+
if reply_markup is None or isinstance(reply_markup, dict):
|
|
49
|
+
return reply_markup
|
|
50
|
+
for method_name, kwargs in (
|
|
51
|
+
("model_dump", {"mode": "json", "exclude_none": True}),
|
|
52
|
+
("to_dict", {}),
|
|
53
|
+
):
|
|
54
|
+
method = getattr(reply_markup, method_name, None)
|
|
55
|
+
if callable(method):
|
|
56
|
+
try:
|
|
57
|
+
return method(**kwargs)
|
|
58
|
+
except TypeError:
|
|
59
|
+
try:
|
|
60
|
+
return method()
|
|
61
|
+
except Exception:
|
|
62
|
+
continue
|
|
63
|
+
except Exception:
|
|
64
|
+
continue
|
|
65
|
+
return reply_markup
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def send_rich_html_via_http(
|
|
69
|
+
token,
|
|
70
|
+
chat_id,
|
|
71
|
+
html_text: str,
|
|
72
|
+
*,
|
|
73
|
+
markdown: bool = False,
|
|
74
|
+
reply_markup: dict = None,
|
|
75
|
+
reply_parameters: dict = None,
|
|
76
|
+
message_thread_id=None,
|
|
77
|
+
disable_notification=None,
|
|
78
|
+
protect_content=None,
|
|
79
|
+
api_base=None,
|
|
80
|
+
):
|
|
81
|
+
"""POST ``sendRichMessage``, falling back to plain ``sendMessage``.
|
|
82
|
+
|
|
83
|
+
``reply_markup``/``reply_parameters`` are plain JSON-able dicts (the
|
|
84
|
+
Bot API wire shape) — a caller holding a framework-native keyboard
|
|
85
|
+
object converts it first (aiogram: ``.model_dump(mode="json",
|
|
86
|
+
exclude_none=True)``; python-telegram-bot: ``.to_dict()``).
|
|
87
|
+
|
|
88
|
+
Returns the Bot API ``result`` dict (with ``message_id``) from
|
|
89
|
+
whichever call succeeds, or ``None`` if both fail.
|
|
90
|
+
"""
|
|
91
|
+
if not html_text:
|
|
92
|
+
return None
|
|
93
|
+
wants_rich = bool(markdown or _has_rich_only_tags(html_text))
|
|
94
|
+
rich_message = (
|
|
95
|
+
{"markdown": html_text} if markdown else {"html": _normalize_html(html_text)}
|
|
96
|
+
)
|
|
97
|
+
shared = _only_set(
|
|
98
|
+
reply_markup=reply_markup,
|
|
99
|
+
reply_parameters=reply_parameters,
|
|
100
|
+
message_thread_id=message_thread_id,
|
|
101
|
+
disable_notification=disable_notification,
|
|
102
|
+
protect_content=protect_content,
|
|
103
|
+
)
|
|
104
|
+
if wants_rich:
|
|
105
|
+
try:
|
|
106
|
+
return await _call_bot_api(
|
|
107
|
+
None,
|
|
108
|
+
"sendRichMessage",
|
|
109
|
+
{"chat_id": chat_id, "rich_message": rich_message, **shared},
|
|
110
|
+
token=token,
|
|
111
|
+
api_base=api_base,
|
|
112
|
+
)
|
|
113
|
+
except WidgetAPIError as e:
|
|
114
|
+
logger.debug(f"[send_rich_html_via_http] rich send rejected, falling back: {e}")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.debug(f"[send_rich_html_via_http] rich send failed, falling back: {e}")
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
return await _call_bot_api(
|
|
120
|
+
None,
|
|
121
|
+
"sendMessage",
|
|
122
|
+
{
|
|
123
|
+
"chat_id": chat_id,
|
|
124
|
+
"text": html_text if markdown else _plain_fallback(html_text),
|
|
125
|
+
"parse_mode": "Markdown" if markdown else "HTML",
|
|
126
|
+
**shared,
|
|
127
|
+
},
|
|
128
|
+
token=token,
|
|
129
|
+
api_base=api_base,
|
|
130
|
+
)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.debug(f"[send_rich_html_via_http] plain send failed: {e}")
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def edit_rich_html_via_http(
|
|
137
|
+
token,
|
|
138
|
+
chat_id,
|
|
139
|
+
message_id,
|
|
140
|
+
html_text: str,
|
|
141
|
+
*,
|
|
142
|
+
markdown: bool = False,
|
|
143
|
+
reply_markup: dict = None,
|
|
144
|
+
api_base=None,
|
|
145
|
+
):
|
|
146
|
+
"""POST ``editMessageText``, optimistically with ``rich_message`` set
|
|
147
|
+
first (see the module docstring: unconfirmed, may always fall
|
|
148
|
+
through), then a plain edit if that is rejected or fails outright.
|
|
149
|
+
"""
|
|
150
|
+
if not html_text:
|
|
151
|
+
return None
|
|
152
|
+
wants_rich = bool(markdown or _has_rich_only_tags(html_text))
|
|
153
|
+
rich_message = (
|
|
154
|
+
{"markdown": html_text} if markdown else {"html": _normalize_html(html_text)}
|
|
155
|
+
)
|
|
156
|
+
shared = _only_set(reply_markup=reply_markup)
|
|
157
|
+
if wants_rich:
|
|
158
|
+
try:
|
|
159
|
+
return await _call_bot_api(
|
|
160
|
+
None,
|
|
161
|
+
"editMessageText",
|
|
162
|
+
{
|
|
163
|
+
"chat_id": chat_id,
|
|
164
|
+
"message_id": message_id,
|
|
165
|
+
"rich_message": rich_message,
|
|
166
|
+
**shared,
|
|
167
|
+
},
|
|
168
|
+
token=token,
|
|
169
|
+
api_base=api_base,
|
|
170
|
+
)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.debug(f"[edit_rich_html_via_http] rich edit rejected, falling back: {e}")
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
return await _call_bot_api(
|
|
176
|
+
None,
|
|
177
|
+
"editMessageText",
|
|
178
|
+
{
|
|
179
|
+
"chat_id": chat_id,
|
|
180
|
+
"message_id": message_id,
|
|
181
|
+
"text": html_text if markdown else _plain_fallback(html_text),
|
|
182
|
+
"parse_mode": "Markdown" if markdown else "HTML",
|
|
183
|
+
**shared,
|
|
184
|
+
},
|
|
185
|
+
token=token,
|
|
186
|
+
api_base=api_base,
|
|
187
|
+
)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.debug(f"[edit_rich_html_via_http] plain edit failed: {e}")
|
|
190
|
+
return None
|
nextgram/ptb_rich.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""nextgram.ptb_rich: rich messages and widgets for python-telegram-bot bots.
|
|
2
|
+
|
|
3
|
+
python-telegram-bot is optional — importing ``nextgram`` never requires
|
|
4
|
+
it, and this module has no import-time dependency on the ``telegram``
|
|
5
|
+
package itself (it only reads a few attributes off whatever ``Bot``/
|
|
6
|
+
``Message`` objects are passed in). Rich-message sending goes over the
|
|
7
|
+
plain Bot HTTP API via ``nextgram.http_rich`` (see that module's docstring
|
|
8
|
+
for exactly what is confirmed vs. speculative); widgets go over the same
|
|
9
|
+
HTTP API via ``nextgram.widgets``, which already works with any client
|
|
10
|
+
exposing a ``.token`` attribute — python-telegram-bot's ``Bot`` does.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from .http_rich import coerce_markup_to_dict, edit_rich_html_via_http, send_rich_html_via_http
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("nextgram.ptb_rich")
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ptb_bot_token",
|
|
22
|
+
"ptb_rich_send",
|
|
23
|
+
"ptb_rich_reply",
|
|
24
|
+
"ptb_rich_edit",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ptb_bot_token(bot) -> str:
|
|
29
|
+
"""Pull the bot token off a python-telegram-bot ``Bot`` instance."""
|
|
30
|
+
token = getattr(bot, "token", None)
|
|
31
|
+
if not token:
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
"ptb_bot_token needs a Bot instance with a .token attribute"
|
|
34
|
+
)
|
|
35
|
+
return token
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def ptb_rich_send(
|
|
39
|
+
bot,
|
|
40
|
+
chat_id,
|
|
41
|
+
html_text: str,
|
|
42
|
+
*,
|
|
43
|
+
markdown: bool = False,
|
|
44
|
+
reply_markup=None,
|
|
45
|
+
reply_to_message_id=None,
|
|
46
|
+
message_thread_id=None,
|
|
47
|
+
disable_notification=None,
|
|
48
|
+
protect_content=None,
|
|
49
|
+
):
|
|
50
|
+
"""Send rich HTML/Markdown to ``chat_id`` with a python-telegram-bot
|
|
51
|
+
``Bot`` (or ``Bot``-like object — ``context.bot`` works the same way).
|
|
52
|
+
|
|
53
|
+
``reply_markup`` accepts a python-telegram-bot ``InlineKeyboardMarkup``
|
|
54
|
+
(or any object with ``.to_dict``) as well as a plain dict. Returns the
|
|
55
|
+
Bot API ``result`` dict (not a ``telegram.Message``) — use
|
|
56
|
+
``result["message_id"]`` for the sent message's id.
|
|
57
|
+
"""
|
|
58
|
+
token = ptb_bot_token(bot)
|
|
59
|
+
reply_parameters = (
|
|
60
|
+
{"message_id": reply_to_message_id} if reply_to_message_id else None
|
|
61
|
+
)
|
|
62
|
+
return await send_rich_html_via_http(
|
|
63
|
+
token,
|
|
64
|
+
chat_id,
|
|
65
|
+
html_text,
|
|
66
|
+
markdown=markdown,
|
|
67
|
+
reply_markup=coerce_markup_to_dict(reply_markup),
|
|
68
|
+
reply_parameters=reply_parameters,
|
|
69
|
+
message_thread_id=message_thread_id,
|
|
70
|
+
disable_notification=disable_notification,
|
|
71
|
+
protect_content=protect_content,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def ptb_rich_reply(message, html_text: str, *, bot=None, **kwargs):
|
|
76
|
+
"""Reply to a python-telegram-bot ``Message``/``Update`` with rich
|
|
77
|
+
HTML/Markdown.
|
|
78
|
+
|
|
79
|
+
Accepts either a ``telegram.Message`` or a ``telegram.Update`` (its
|
|
80
|
+
``.effective_message`` is used). ``bot`` defaults to
|
|
81
|
+
``message.get_bot()`` when the object exposes that (python-telegram-bot
|
|
82
|
+
messages do).
|
|
83
|
+
"""
|
|
84
|
+
target = getattr(message, "effective_message", None) or message
|
|
85
|
+
app = bot
|
|
86
|
+
if app is None:
|
|
87
|
+
get_bot = getattr(target, "get_bot", None)
|
|
88
|
+
app = get_bot() if callable(get_bot) else getattr(target, "_bot", None)
|
|
89
|
+
chat = getattr(getattr(target, "chat", None), "id", None) or getattr(
|
|
90
|
+
target, "chat_id", None
|
|
91
|
+
)
|
|
92
|
+
if app is None or chat is None:
|
|
93
|
+
logger.debug("[ptb_rich_reply] missing bot/chat on message")
|
|
94
|
+
return None
|
|
95
|
+
return await ptb_rich_send(
|
|
96
|
+
app, chat, html_text, reply_to_message_id=getattr(target, "message_id", None), **kwargs
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def ptb_rich_edit(
|
|
101
|
+
message,
|
|
102
|
+
html_text: str,
|
|
103
|
+
*,
|
|
104
|
+
bot=None,
|
|
105
|
+
chat_id=None,
|
|
106
|
+
message_id=None,
|
|
107
|
+
markdown: bool = False,
|
|
108
|
+
reply_markup=None,
|
|
109
|
+
):
|
|
110
|
+
"""Edit a message to rich HTML/Markdown with a python-telegram-bot
|
|
111
|
+
``Bot``.
|
|
112
|
+
|
|
113
|
+
``message`` is a ``telegram.Message`` (``.chat_id``/``.message_id``
|
|
114
|
+
read automatically) or ``None``, in which case ``chat_id`` and
|
|
115
|
+
``message_id`` are required.
|
|
116
|
+
"""
|
|
117
|
+
if message is not None:
|
|
118
|
+
if bot is None:
|
|
119
|
+
get_bot = getattr(message, "get_bot", None)
|
|
120
|
+
bot = get_bot() if callable(get_bot) else getattr(message, "_bot", None)
|
|
121
|
+
chat_id = getattr(getattr(message, "chat", None), "id", None) or getattr(
|
|
122
|
+
message, "chat_id", None
|
|
123
|
+
)
|
|
124
|
+
message_id = getattr(message, "message_id", None)
|
|
125
|
+
app = bot
|
|
126
|
+
if app is None or chat_id is None or message_id is None:
|
|
127
|
+
logger.debug("[ptb_rich_edit] missing bot/chat_id/message_id")
|
|
128
|
+
return None
|
|
129
|
+
token = ptb_bot_token(app)
|
|
130
|
+
return await edit_rich_html_via_http(
|
|
131
|
+
token,
|
|
132
|
+
chat_id,
|
|
133
|
+
message_id,
|
|
134
|
+
html_text,
|
|
135
|
+
markdown=markdown,
|
|
136
|
+
reply_markup=coerce_markup_to_dict(reply_markup),
|
|
137
|
+
)
|