From 4f771ceefd1cf538add07e49e19469a825ac51e4 Mon Sep 17 00:00:00 2001 From: xuu33030 <268775543+xuu33030@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:15:11 +0800 Subject: [PATCH 1/3] ptbcontrib/extract_urls and ptbcontrib/longbotcommand: bump PTB dependency to ~=20.0 and adjust tests for v20 immutability --- ptbcontrib/extract_urls/requirements.txt | 2 +- ptbcontrib/longbotcommand/requirements.txt | 2 +- tests/test_extract_urls.py | 22 ++++++++++++++++++++++ tests/test_longbotcommand.py | 14 +++++++++----- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/ptbcontrib/extract_urls/requirements.txt b/ptbcontrib/extract_urls/requirements.txt index 79bcdbf..9b83146 100644 --- a/ptbcontrib/extract_urls/requirements.txt +++ b/ptbcontrib/extract_urls/requirements.txt @@ -1 +1 @@ -python-telegram-bot>=12.0 \ No newline at end of file +python-telegram-bot~=20.0 \ No newline at end of file diff --git a/ptbcontrib/longbotcommand/requirements.txt b/ptbcontrib/longbotcommand/requirements.txt index 93aeff3..9b83146 100644 --- a/ptbcontrib/longbotcommand/requirements.txt +++ b/ptbcontrib/longbotcommand/requirements.txt @@ -1 +1 @@ -python-telegram-bot>=13.0,<20 \ No newline at end of file +python-telegram-bot~=20.0 \ No newline at end of file diff --git a/tests/test_extract_urls.py b/tests/test_extract_urls.py index 1aaba5e..c63163c 100644 --- a/tests/test_extract_urls.py +++ b/tests/test_extract_urls.py @@ -18,6 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import pytest from telegram import Message, MessageEntity +from telegram.constants import MessageEntityType from ptbcontrib import extract_urls @@ -65,6 +66,27 @@ def test_extract_urls_caption(self): assert test_entities[0]["url"] == results[0] assert test_entities[2]["url"] == results[1] + def test_extract_urls_entity_type_constants(self): + test_entities = [ + { + "length": 6, + "offset": 0, + "type": MessageEntityType.TEXT_LINK, + "url": "http://github.com", + }, + {"length": 17, "offset": 23, "type": MessageEntityType.URL}, + ] + test_message = Message( + message_id=1, + from_user=None, + date=None, + chat=None, + text="Github can be found at http://github.com.", + entities=[MessageEntity(**entity) for entity in test_entities], + ) + + assert extract_urls.extract_urls(test_message) == ["http://github.com"] + def test_extract_urls_order(self): test_entities = [ {"length": 6, "offset": 0, "type": "text_link", "url": "http://github.com"}, diff --git a/tests/test_longbotcommand.py b/tests/test_longbotcommand.py index 76fa280..dc7c376 100644 --- a/tests/test_longbotcommand.py +++ b/tests/test_longbotcommand.py @@ -48,12 +48,16 @@ def test_long_desc_init(self, long_desc_command): assert long_desc_command.description == "desc" assert long_desc_command.long_description == "long desc" - def test_short_desc_change(self, short_desc_command): - short_desc_command.description = "new desc" - assert short_desc_command.long_description == "new desc" + def test_short_desc_immutable(self, short_desc_command): + with pytest.raises(AttributeError): + short_desc_command.description = "new desc" + + assert short_desc_command.long_description == "desc" + + def test_long_desc_immutable(self, long_desc_command): + with pytest.raises(AttributeError): + long_desc_command.description = "new desc" - def test_long_desc_change(self, long_desc_command): - long_desc_command.description = "new desc" assert long_desc_command.long_description == "long desc" def test_short_desc_dict(self, short_desc_command, bot_command): From baea11c12bb65d8b4212e68b762ce76efa447bb8 Mon Sep 17 00:00:00 2001 From: xuu33030 <268775543+xuu33030@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:17:53 +0800 Subject: [PATCH 2/3] fix: update contributions for PTB v20 --- ptbcontrib/extract_urls/README.md | 2 +- ptbcontrib/longbotcommand/README.md | 59 ++++++++++++++++------------- tests/test_extract_urls.py | 7 +++- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/ptbcontrib/extract_urls/README.md b/ptbcontrib/extract_urls/README.md index 7e1a6d8..f49b2be 100644 --- a/ptbcontrib/extract_urls/README.md +++ b/ptbcontrib/extract_urls/README.md @@ -10,7 +10,7 @@ Please see the docstrings for more details. ## Requirements -* `python-telegram-bot>=12.0` +* `python-telegram-bot~=20.0` ## Authors diff --git a/ptbcontrib/longbotcommand/README.md b/ptbcontrib/longbotcommand/README.md index 8ed8f61..e2f5d00 100644 --- a/ptbcontrib/longbotcommand/README.md +++ b/ptbcontrib/longbotcommand/README.md @@ -4,56 +4,61 @@ Provides a `LongBotCommand` class that allows you to store a second, longer desc Example: ```python -from typing import List - from telegram import Update -from telegram.ext import Updater, CommandHandler, CallbackContext -from ptbcontrib.longbotcommand import LongBotCommand +from telegram.ext import Application, CommandHandler, ContextTypes -updater = Updater("TOKEN", use_context=True) -dp = updater.dispatcher +from ptbcontrib.longbotcommand import LongBotCommand -lorem_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " - "incididunt ut labore et dolore magna aliqua." +lorem_text = ( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + "incididunt ut labore et dolore magna aliqua." +) -lorem_desc = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. " - "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an " - "unknown printer took a galley of type and scrambled it to make a type specimen book. " - "It has survived not only five centuries, but also the leap into electronic typesetting, " - "remaining essentially unchanged." +lorem_desc = ( + "Lorem Ipsum is simply dummy text of the printing and typesetting industry. " + "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an " + "unknown printer took a galley of type and scrambled it to make a type specimen book. " + "It has survived not only five centuries, but also the leap into electronic typesetting, " + "remaining essentially unchanged." +) -BOT_COMMANDS: List[LongBotCommand] = [ +BOT_COMMANDS = [ LongBotCommand("help", "Prints out a list of available commands"), LongBotCommand( "lorem", "Prints Lorem Ipsum", - long_description = lorem_desc, + long_description=lorem_desc, ), ] -def help(update: Update, context: CallbackContext) -> None: - for command in context.bot.commands: - # This will only work if you already used set_my_commands! - update.message.reply_text(f"/{command.command}\n\n{command.long_description}") -def lorem(update: Update, context: CallbackContext) -> None: - update.message.reply_text(lorem_text) +async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + commands = await context.bot.get_my_commands() + descriptions = {command.command: command.long_description for command in BOT_COMMANDS} + await update.effective_message.reply_text( + "\n\n".join(f"/{command.command}\n\n{descriptions[command.command]}" for command in commands) + ) + + +async def lorem(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.effective_message.reply_text(lorem_text) -dp.add_handler(CommandHandler("help", help)) -dp.add_handler(CommandHandler("lorem", lorem)) +async def post_init(application: Application) -> None: + await application.bot.set_my_commands(BOT_COMMANDS) -dp.bot.set_my_commands(BOT_COMMANDS) -updater.start_polling() +application = Application.builder().token("TOKEN").post_init(post_init).build() +application.add_handler(CommandHandler("help", help_command)) +application.add_handler(CommandHandler("lorem", lorem)) -updater.idle() +application.run_polling() ``` ## Requirements -* `20>python-telegram-bot>=13.0` +* `python-telegram-bot~=20.0` ## Authors diff --git a/tests/test_extract_urls.py b/tests/test_extract_urls.py index c63163c..e203c6c 100644 --- a/tests/test_extract_urls.py +++ b/tests/test_extract_urls.py @@ -81,11 +81,14 @@ def test_extract_urls_entity_type_constants(self): from_user=None, date=None, chat=None, - text="Github can be found at http://github.com.", + text="Github can be found at http://google.com.", entities=[MessageEntity(**entity) for entity in test_entities], ) - assert extract_urls.extract_urls(test_message) == ["http://github.com"] + assert extract_urls.extract_urls(test_message) == [ + "http://github.com", + "http://google.com", + ] def test_extract_urls_order(self): test_entities = [ From 68766906adb524e766b3df6a064b8f39e9d272a3 Mon Sep 17 00:00:00 2001 From: xuu33030 <268775543+xuu33030@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:35:01 +0800 Subject: [PATCH 3/3] Address PTB v20 compatibility review comments --- ptbcontrib/extract_urls/extracturls.py | 9 +++++---- ptbcontrib/longbotcommand/README.md | 27 ++++++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/ptbcontrib/extract_urls/extracturls.py b/ptbcontrib/extract_urls/extracturls.py index c08eda8..aaced69 100644 --- a/ptbcontrib/extract_urls/extracturls.py +++ b/ptbcontrib/extract_urls/extracturls.py @@ -17,13 +17,14 @@ # 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 helper functions to extract URLs from messages.""" +from __future__ import annotations + import re -from typing import Dict, List from telegram import Message, MessageEntity -def extract_urls(message: Message) -> List[str]: +def extract_urls(message: Message) -> list[str]: """ Extracts all hyperlinks that are contained in a message. This includes message entities and the media caption, i.e. while of course only text *or* caption is present this works for both. @@ -50,7 +51,7 @@ def extract_urls(message: Message) -> List[str]: results[key] = key.url # Remove exact duplicates and keep the first appearance - filtered_results: Dict[str, MessageEntity] = {} + filtered_results: dict[str, MessageEntity] = {} for key, value in results.items(): if not filtered_results.get(value): filtered_results[value] = key @@ -66,7 +67,7 @@ def extract_urls(message: Message) -> List[str]: def extract_message_links( message: Message, private_only: bool = False, public_only: bool = False -) -> List[str]: +) -> list[str]: """ Extracts all message links that are contained in a message. This includes message entities and the media caption, i.e. while of course only text *or* caption is present this works for both. diff --git a/ptbcontrib/longbotcommand/README.md b/ptbcontrib/longbotcommand/README.md index e2f5d00..ac0daea 100644 --- a/ptbcontrib/longbotcommand/README.md +++ b/ptbcontrib/longbotcommand/README.md @@ -9,25 +9,18 @@ from telegram.ext import Application, CommandHandler, ContextTypes from ptbcontrib.longbotcommand import LongBotCommand -lorem_text = ( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " - "incididunt ut labore et dolore magna aliqua." -) - -lorem_desc = ( - "Lorem Ipsum is simply dummy text of the printing and typesetting industry. " - "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an " - "unknown printer took a galley of type and scrambled it to make a type specimen book. " - "It has survived not only five centuries, but also the leap into electronic typesetting, " - "remaining essentially unchanged." +details_text = "This bot shows how to attach a longer help description to a command." +details_desc = ( + "Show the details of this example bot. The longer description appears in /help, while the " + "short description is sent to Telegram's command menu." ) BOT_COMMANDS = [ LongBotCommand("help", "Prints out a list of available commands"), LongBotCommand( - "lorem", - "Prints Lorem Ipsum", - long_description=lorem_desc, + "details", + "Show bot details", + long_description=details_desc, ), ] @@ -40,8 +33,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ) -async def lorem(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - await update.effective_message.reply_text(lorem_text) +async def details(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.effective_message.reply_text(details_text) async def post_init(application: Application) -> None: @@ -50,7 +43,7 @@ async def post_init(application: Application) -> None: application = Application.builder().token("TOKEN").post_init(post_init).build() application.add_handler(CommandHandler("help", help_command)) -application.add_handler(CommandHandler("lorem", lorem)) +application.add_handler(CommandHandler("details", details)) application.run_polling()