<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Telegram Bot Engineering Note</title>
    <description>The latest articles on DEV Community by Telegram Bot Engineering Note (@telegram_bot_notes).</description>
    <link>https://dev.to/telegram_bot_notes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082007%2F6eef430b-a3e2-4019-b155-ebec3a9fb2d7.jpg</url>
      <title>DEV Community: Telegram Bot Engineering Note</title>
      <link>https://dev.to/telegram_bot_notes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/telegram_bot_notes"/>
    <language>en</language>
    <item>
      <title>Telegram频道适合发布哪些内容？</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Fri, 28 Aug 2026 05:46:18 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/telegrampin-dao-gua-he-fa-bu-na-xie-nei-rong--4c79</link>
      <guid>https://dev.to/telegram_bot_notes/telegrampin-dao-gua-he-fa-bu-na-xie-nei-rong--4c79</guid>
      <description>&lt;p&gt;Telegram频道适合持续发布面向订阅者的信息，例如行业资讯、课程通知、活动安排、产品动态、作品内容和兴趣主题整理。频道通常由创建者或管理员发布内容，订阅者以阅读为主，因此比开放群组更适合集中传递重要消息。发布前应先确定频道主题与更新频率，让订阅者清楚自己可以获得哪些内容。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkj8qqs0z249a6d6bzq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkj8qqs0z249a6d6bzq8.png" alt=" " width="236" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  发布行业资讯与知识整理
&lt;/h2&gt;

&lt;p&gt;科技、教育、设计、游戏、语言学习和职场等领域，都可以通过频道持续整理资讯。运营者可发布新闻摘要、工具介绍、经验分享、书籍推荐和实用链接，帮助订阅者更快找到相关信息。&lt;/p&gt;

&lt;p&gt;资讯内容应注明来源和发布时间，避免把未经核实的传闻写成事实。转载文章、图片或视频时，也应注意版权范围。相比单纯复制大量内容，加入简短的背景说明、适用人群和注意事项，更容易让读者理解信息价值。&lt;/p&gt;

&lt;h2&gt;
  
  
  发布课程与学习通知
&lt;/h2&gt;

&lt;p&gt;教师、培训机构或学习社群可以用频道发布课程时间、作业要求、资料更新和直播提醒。频道可作为统一通知入口，减少信息分散在多个聊天群里后被遗漏的情况。&lt;/p&gt;

&lt;p&gt;发布学习资料时，建议按课程、日期或主题建立固定格式。例如先写课程名称和适用对象，再放文件或链接，并说明截止时间。重要通知可以置顶，方便新加入的订阅者快速找到。涉及学生资料时，不应公开姓名、成绩和联系方式。&lt;/p&gt;

&lt;h2&gt;
  
  
  发布活动与社群公告
&lt;/h2&gt;

&lt;p&gt;线上讲座、线下聚会、游戏活动和兴趣社群都可以通过频道发布报名时间、地点、参与规则和后续提醒。对成员较多的社群来说，频道比群组更适合发送统一公告，能够避免重要信息被日常聊天淹没。&lt;/p&gt;

&lt;p&gt;活动内容发生变化时，应及时发布更新，并在原通知中注明调整原因。若需要收集报名信息，可使用可信的表单工具，并明确说明信息用途。不要要求订阅者在公开频道中提交身份证号、付款密码或其他敏感资料。&lt;/p&gt;

&lt;h2&gt;
  
  
  分享作品与品牌动态
&lt;/h2&gt;

&lt;p&gt;创作者、开发者、小型团队和个人品牌可以在频道发布新作品、更新日志、案例展示、使用技巧和幕后内容。固定的栏目形式，例如每周精选、常见问题、更新说明或用户反馈整理，有助于读者形成稳定预期。&lt;/p&gt;

&lt;p&gt;品牌宣传内容应避免只发布广告。除了介绍产品，也可以提供解决问题的方法、使用场景和公开资料，让订阅者获得实际价值。对于合作推广或付费内容，应清楚标注，避免让读者误以为是普通推荐。&lt;/p&gt;

&lt;h2&gt;
  
  
  发布文件与实用资源
&lt;/h2&gt;

&lt;p&gt;频道支持发布图片、视频、文档和链接，适合整理模板、清单、公开资料和常用工具。运营者可以为每份资源补充用途、版本、更新时间和使用限制，减少订阅者下载后不知道如何使用的问题。&lt;/p&gt;

&lt;p&gt;涉及软件安装包或外部链接时，应优先提供可信来源，并提醒订阅者核对文件名称、发布者和权限请求。不要在频道中传播来历不明的程序、破解工具、盗版资源或可能损害设备安全的文件。&lt;/p&gt;

&lt;h2&gt;
  
  
  用频道配合讨论群组
&lt;/h2&gt;

&lt;p&gt;频道适合单向发布，群组更适合成员互动。若运营者既需要稳定公告，又希望收集读者意见，可以建立频道发布重要内容，再设置讨论群组处理提问和交流。这样能让公告与聊天分开，管理也更清楚。&lt;/p&gt;

&lt;p&gt;新用户可通过&lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt;查看适用于Windows、macOS、Android及iOS设备的客户端版本，并获取频道、群组与中文设置说明。创建频道后，建议先确定主题、发布节奏和内容规范，再根据订阅者反馈逐步调整。&lt;/p&gt;

&lt;p&gt;总体来看，Telegram频道适合发布有明确主题、可持续更新且对订阅者有帮助的内容。无论是资讯、课程、公告、作品还是资源，保持信息真实、结构清楚和更新稳定，才能让频道长期获得读者关注。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Telegram是否支持创建大型群组？</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Fri, 28 Aug 2026 05:12:46 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/telegramshi-fou-zhi-chi-chuang-jian-da-xing-qun-zu--534o</link>
      <guid>https://dev.to/telegram_bot_notes/telegramshi-fou-zhi-chi-chuang-jian-da-xing-qun-zu--534o</guid>
      <description>&lt;p&gt;Telegram支持创建大型群组，适合用于兴趣社群、学习讨论、远程协作和活动通知等场景。群主和管理员可以设置成员权限、邀请方式、发言规则及内容管理范围，让人数较多的交流保持相对有序。建立群组前，建议先明确主题、成员来源和管理方式，避免群组人数增加后出现广告、骚扰或无关内容过多的问题。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwpaerdu822yrnlgicj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwpaerdu822yrnlgicj5.png" alt=" " width="236" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  大型群组适合哪些使用场景
&lt;/h2&gt;

&lt;p&gt;大型群组适合需要多人持续交流的场景，例如课程学习、行业讨论、游戏社群、同城活动和项目协作。成员可以发送文字、图片、文件、链接和表情，也能围绕某条消息进行回复，使讨论内容更容易追溯。&lt;/p&gt;

&lt;p&gt;如果群组主要用于发布通知，而不是开放讨论，可考虑限制普通成员的发言权限。这样能减少重复提问和无关聊天，让重要信息更容易被看到。需要成员互动时，则可根据主题设置不同的讨论区域或子群组。&lt;/p&gt;

&lt;h2&gt;
  
  
  建群前先规划群组规则
&lt;/h2&gt;

&lt;p&gt;人数较多的群组若没有清晰规则，很容易被广告、刷屏和争吵影响。群主可在群公告或置顶消息中说明群组主题、允许发布的内容、禁止行为和违规处理方式，并在新成员加入后提醒阅读。&lt;/p&gt;

&lt;p&gt;规则应尽量具体，例如是否允许推广链接、是否允许发布重复内容、遇到问题应如何提问，以及管理员如何处理违规消息。规则不是为了限制正常交流，而是帮助成员理解边界，降低管理人员反复解释的成本。&lt;/p&gt;

&lt;h2&gt;
  
  
  管理员权限需要合理分配
&lt;/h2&gt;

&lt;p&gt;群主可以根据实际需要添加管理员，并分别设置管理权限。负责审核新成员的人不一定需要删除消息权限，负责整理公告的人也不一定需要邀请成员权限。按职责分配权限，能减少误操作和账号被盗后带来的影响。&lt;/p&gt;

&lt;p&gt;管理员账号应开启两步验证，并避免把验证码、密码或恢复邮箱信息分享给他人。成员较多时，可定期检查管理员列表，及时移除已经不再参与管理的账号。对来源不明的管理机器人，也应谨慎授权。&lt;/p&gt;

&lt;h2&gt;
  
  
  控制成员加入方式
&lt;/h2&gt;

&lt;p&gt;大型群组可以通过邀请链接、公开搜索或其他方式吸引成员，但不同加入方式带来的管理压力不同。公开群组更容易被陌生用户找到，也更容易出现广告和垃圾账号；邀请制群组则适合成员来源明确的学习班、团队或朋友圈。&lt;/p&gt;

&lt;p&gt;管理员可以按需要调整邀请权限，必要时要求新成员经过审核后再加入。活动结束后，如果邀请链接被大量转发，也应及时更换或关闭旧链接，避免无关人员持续进入群组。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用工具辅助维护秩序
&lt;/h2&gt;

&lt;p&gt;成员不断增加后，仅靠人工查看每一条消息会比较困难。管理员可使用平台提供的权限设置、慢速模式、置顶消息和举报功能维护秩序。对于重复广告、恶意链接和明显骚扰行为，应尽快处理，避免其他成员模仿。&lt;/p&gt;

&lt;p&gt;机器人可以协助完成欢迎提示、关键词过滤和基础问答，但不应完全代替人工管理。设置机器人前，应了解其权限范围和数据处理方式，避免让机器人读取与群组目标无关的内容。&lt;/p&gt;

&lt;h2&gt;
  
  
  区分群组与频道的用途
&lt;/h2&gt;

&lt;p&gt;群组主要用于成员之间的互动和讨论，频道更适合由管理员向大量订阅者单向发布内容。若社群既要发布重要公告，又要收集成员反馈，可以将频道和讨论群组配合使用，让不同内容进入更合适的位置。&lt;/p&gt;

&lt;p&gt;新用户可通过&lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt;查看适用于Windows、macOS、Android及iOS设备的客户端版本，并了解群组、频道与基础设置说明。创建大型群组后，应先从清晰的主题和适度的成员规模开始，再逐步完善规则与管理权限。&lt;/p&gt;

&lt;p&gt;总体来说，Telegram适合创建和管理大型群组，但群组规模越大，越需要明确规则、分配管理员权限并控制加入方式。只有在交流目标和管理机制都清楚的情况下，群组才能长期保持有价值的讨论环境。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Handle Telegram Bot Rate Limits with Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Wed, 26 Aug 2026 02:51:36 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-handle-telegram-bot-rate-limits-with-python-k8b</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-handle-telegram-bot-rate-limits-with-python-k8b</guid>
      <description>&lt;p&gt;A Telegram bot may work correctly during development but begin returning errors when it sends notifications to many users. One of the most common responses is &lt;code&gt;429 Too Many Requests&lt;/code&gt;, which means the bot has exceeded a flood-control limit.&lt;/p&gt;

&lt;p&gt;The correct solution is not to resend the same request continuously. A reliable bot should queue outgoing messages, control its sending speed, read Telegram’s &lt;code&gt;retry_after&lt;/code&gt; value, and retry failed requests after the required delay.&lt;/p&gt;

&lt;p&gt;This tutorial demonstrates how to build a basic rate-limited Telegram message sender with Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Telegram Bot Rate Limits
&lt;/h2&gt;

&lt;p&gt;Telegram applies different restrictions depending on the destination and sending pattern. According to the &lt;a href="https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this" rel="noopener noreferrer"&gt;official Telegram Bots FAQ&lt;/a&gt;, developers should generally avoid sending more than one message per second to a single chat. Group messages and bulk broadcasts have separate limits.&lt;/p&gt;

&lt;p&gt;When a bot exceeds a limit, the API may return a response similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Too Many Requests: retry after 3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retry_after"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;retry_after&lt;/code&gt; field specifies how many seconds the program should wait before repeating the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the Python Project
&lt;/h2&gt;

&lt;p&gt;Create a project directory and install the &lt;code&gt;requests&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;telegram-rate-limiter
&lt;span class="nb"&gt;cd &lt;/span&gt;telegram-rate-limiter

python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows PowerShell users can activate the environment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="n"&gt;\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a bot through &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;BotFather&lt;/a&gt; and store its Token in an environment variable. Never publish a bot Token in an article, screenshot or public repository.&lt;/p&gt;

&lt;p&gt;Linux and macOS users can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_BOT_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows PowerShell users can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_BOT_TOKEN"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local testing, install a mobile client or review the available desktop versions through this &lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram download&lt;/a&gt; page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Telegram API Client
&lt;/h2&gt;

&lt;p&gt;Create a file named &lt;code&gt;sender.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.telegram.org/bot&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sendMessage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function returns the HTTP status code and Telegram’s JSON response. The program can therefore distinguish rate limits from network errors and invalid requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding an Outgoing Message Queue
&lt;/h2&gt;

&lt;p&gt;A queue separates message creation from delivery. Other parts of the application can add messages immediately, while a worker sends them at a controlled speed.&lt;/p&gt;

&lt;p&gt;Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageJob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="n"&gt;message_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new message can now be scheduled without sending it immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;MessageJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123456789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your report is ready.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue preserves the order of the jobs and allows the sending process to operate independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling HTTP 429 Responses
&lt;/h2&gt;

&lt;p&gt;Import &lt;code&gt;time&lt;/code&gt; and create a function that processes each queued job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessageJob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sent message to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limited for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chat: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Telegram API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Telegram returns HTTP 429, the function reads &lt;code&gt;retry_after&lt;/code&gt;, waits for the specified period and returns the job to the queue. This is safer than retrying immediately or using the same arbitrary delay for every error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Background Worker
&lt;/h2&gt;

&lt;p&gt;The queue needs a worker that continuously processes pending jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;

&lt;span class="n"&gt;GLOBAL_INTERVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_worker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;process_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Network error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;RuntimeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GLOBAL_INTERVAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker uses exponential backoff for temporary network errors. The first retry waits two seconds, the next waits four seconds, and the final retry waits eight seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Bursts to the Same Chat
&lt;/h2&gt;

&lt;p&gt;A bot may send messages to different users successfully while still exceeding the limit for one specific conversation. Store the latest delivery time for every chat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;last_sent_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;PER_CHAT_INTERVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_sent_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PER_CHAT_INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_sent_at&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call this function before making the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessageJob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;wait_for_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sent message to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Telegram API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an application with multiple worker processes, per-chat timestamps should be stored in a shared system such as Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Queue
&lt;/h2&gt;

&lt;p&gt;Add several test jobs at the bottom of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;target_chat_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123456789&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;MessageJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Queued message number &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;message_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All jobs completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;target_chat_id&lt;/code&gt; with your own chat ID, start a conversation with the bot and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python sender.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The messages should be delivered in order instead of being sent as one uncontrolled burst.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Production Improvements
&lt;/h2&gt;

&lt;p&gt;This example stores jobs in memory. If the Python process stops, pending messages will be lost. A production application should use a persistent queue such as Redis, RabbitMQ, Amazon SQS or a database-backed job system.&lt;/p&gt;

&lt;p&gt;It should also include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured logging without exposing the bot Token&lt;/li&gt;
&lt;li&gt;A maximum retry count for permanent failures&lt;/li&gt;
&lt;li&gt;Separate handling for blocked users and invalid chat IDs&lt;/li&gt;
&lt;li&gt;Graceful shutdown logic&lt;/li&gt;
&lt;li&gt;Monitoring for queue size and delivery latency&lt;/li&gt;
&lt;li&gt;Message identifiers to prevent duplicate delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Telegram Bot API documentation&lt;/a&gt; should remain the primary source for current methods, parameters and error responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Handling Telegram rate limits requires more than adding a delay to a loop. A stable bot needs an outgoing queue, per-chat pacing, network-error retries and explicit handling of HTTP 429 responses.&lt;/p&gt;

&lt;p&gt;Once these controls are in place, the same architecture can support monitoring alerts, order updates, scheduled notifications and larger subscriber lists without repeatedly failing during traffic spikes.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Telegram中文语言包与汉化设置攻略</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Sat, 22 Aug 2026 10:26:24 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/telegramzhong-wen-yu-yan-bao-yu-yi-hua-she-zhi-gong-lue-2gjm</link>
      <guid>https://dev.to/telegram_bot_notes/telegramzhong-wen-yu-yan-bao-yu-yi-hua-she-zhi-gong-lue-2gjm</guid>
      <description>&lt;p&gt;Telegram默认界面可能显示英文，但用户不需要重新安装所谓的“汉化版”，通过应用内的语言选项或Telegram语言包即可切换为简体中文、繁体中文、正体中文（台湾）或繁体中文（香港）。整个过程不会删除聊天记录，也不会影响已经加入的群组与频道。&lt;/p&gt;

&lt;p&gt;安装适合设备的Telegram&lt;/p&gt;

&lt;p&gt;开始设置前，应先确认手机或电脑已经安装Telegram客户端。用户可以通过&lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram电脑版&lt;/a&gt;查看Windows、macOS、Android及iOS版本，并按照设备类型完成安装。&lt;/p&gt;

&lt;p&gt;建议尽量使用来源明确的客户端，不要随意安装经过修改的安装包。第三方修改版可能更新不及时，也可能存在账号与隐私风险。&lt;/p&gt;

&lt;p&gt;在手机上切换中文&lt;/p&gt;

&lt;p&gt;Android和iPhone用户可以打开Telegram，依次进入：&lt;/p&gt;

&lt;p&gt;Settings → Language&lt;/p&gt;

&lt;p&gt;进入语言列表后，选择简体中文、繁体中文或其他需要的语言。如果列表中已经提供相应选项，点击后界面会立即切换，一般不需要退出账号。&lt;/p&gt;

&lt;p&gt;不同版本的菜单名称可能略有差异。如果暂时找不到中文，可以先更新Telegram，再重新打开语言设置页面。&lt;/p&gt;

&lt;p&gt;在电脑上设置中文界面&lt;/p&gt;

&lt;p&gt;Telegram Desktop用户可以点击左上角菜单，进入：&lt;/p&gt;

&lt;p&gt;Settings → Language&lt;/p&gt;

&lt;p&gt;选择中文后，菜单、按钮和系统提示会自动切换。如果部分内容仍然显示英文，可以关闭Telegram并重新启动，或者先切换到英文，再重新选择中文。&lt;/p&gt;

&lt;p&gt;语言设置只会改变操作界面，不会自动翻译聊天内容。群组消息、频道文章和联系人发送的文字仍会保持原来的语言。&lt;/p&gt;

&lt;p&gt;使用Telegram中文语言包&lt;/p&gt;

&lt;p&gt;除了应用内置语言，Telegram还设有官方翻译平台。用户可以根据习惯选择不同版本：&lt;/p&gt;

&lt;p&gt;简体中文语言包&lt;br&gt;
繁体中文语言包&lt;br&gt;
正体中文（台湾）语言包&lt;br&gt;
繁体中文（香港）语言包&lt;/p&gt;

&lt;p&gt;打开对应页面后，按照提示在Telegram中应用语言即可。语言包主要修改应用界面的文字，不会读取聊天内容，也不会改变账号资料。&lt;/p&gt;

&lt;p&gt;中文设置没有生效怎么办&lt;/p&gt;

&lt;p&gt;如果应用语言包后仍有部分英文，可以检查Telegram是否为最新版本，并重新启动应用。电脑端和手机端可能需要分别设置，修改手机语言不一定会同步改变电脑端界面。&lt;/p&gt;

&lt;p&gt;部分语言包仍处于持续翻译和更新状态，因此个别新功能可能暂时保留英文。遇到翻译不完整时，可以等待语言包更新，或暂时改用另一个完成度较高的中文版本。&lt;/p&gt;

&lt;p&gt;使用中文语言包要注意什么&lt;/p&gt;

&lt;p&gt;语言包本身只是界面翻译，不会提供额外功能，也不能解除账号限制。对于来源不明的语言包链接，应先核对域名，优先选择Telegram官方翻译平台提供的版本。&lt;/p&gt;

&lt;p&gt;完成设置后，Telegram的聊天、频道、群组、文件传输和通话功能都不会受到影响。用户也可以随时返回Language页面，重新切换为英文或其他语言。对于第一次使用Telegram的人来说，先完成中文界面设置，再调整隐私、通知和储存空间选项，会更容易熟悉各项功能。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Receive and Download Photos and Files with a Telegram Bot in Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:12:55 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-receive-and-download-photos-and-files-with-a-telegram-bot-in-python-1k53</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-receive-and-download-photos-and-files-with-a-telegram-bot-in-python-1k53</guid>
      <description>&lt;p&gt;Telegram bots can do more than reply to text messages. They can receive photos, PDF files, spreadsheets, ZIP archives, and other documents sent by users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0ltl07fsmyy2dts02l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0ltl07fsmyy2dts02l.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." alt="Uploading image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a Python bot that:&lt;/p&gt;

&lt;p&gt;Detects photos and documents&lt;br&gt;
Reads their metadata&lt;br&gt;
Downloads them to a local folder&lt;br&gt;
Creates safe, unique filenames&lt;br&gt;
Rejects unsupported or oversized uploads&lt;br&gt;
Explains the difference between file_id and file_unique_id&lt;/p&gt;

&lt;p&gt;The example uses the asynchronous python-telegram-bot library.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;p&gt;Python 3.10 or newer&lt;br&gt;
A Telegram bot created through BotFather&lt;br&gt;
Your bot token&lt;br&gt;
The python-telegram-bot package&lt;/p&gt;

&lt;p&gt;If Telegram is not yet installed on your test device, use this &lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram download guide&lt;/a&gt; to choose the appropriate mobile or desktop version.&lt;/p&gt;

&lt;p&gt;Install the Python package:&lt;/p&gt;

&lt;p&gt;pip install python-telegram-bot&lt;/p&gt;

&lt;p&gt;Keep your bot token in an environment variable instead of placing it directly inside the source code.&lt;/p&gt;

&lt;p&gt;Linux or macOS:&lt;/p&gt;

&lt;p&gt;export TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN"&lt;/p&gt;

&lt;p&gt;Windows PowerShell:&lt;/p&gt;

&lt;p&gt;$env:TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN"&lt;br&gt;
How Telegram Represents Uploaded Files&lt;/p&gt;

&lt;p&gt;When a user sends media, Telegram does not immediately place the actual file contents inside the update.&lt;/p&gt;

&lt;p&gt;Instead, the update contains information such as:&lt;/p&gt;

&lt;p&gt;file_id: Used by your bot to retrieve or resend the file&lt;br&gt;
file_unique_id: A stable identifier for recognizing the same file&lt;br&gt;
file_size: Approximate file size in bytes&lt;br&gt;
file_name: Original name of a document, when available&lt;br&gt;
mime_type: Reported media type&lt;br&gt;
Photo dimensions and available sizes&lt;/p&gt;

&lt;p&gt;Your bot passes the file_id to Telegram’s getFile method. The returned File object can then be downloaded.&lt;/p&gt;

&lt;p&gt;A photo message normally contains several PhotoSize objects. Telegram generates these versions at different resolutions. The last item is generally the largest available version:&lt;/p&gt;

&lt;p&gt;photo = update.message.photo[-1]&lt;/p&gt;

&lt;p&gt;Documents are available through:&lt;/p&gt;

&lt;p&gt;document = update.message.document&lt;br&gt;
Receiving and Downloading a Photo&lt;/p&gt;

&lt;p&gt;Create a file named bot.py and start with the following handler:&lt;/p&gt;

&lt;p&gt;from pathlib import Path&lt;br&gt;
from uuid import uuid4&lt;/p&gt;

&lt;p&gt;from telegram import Update&lt;br&gt;
from telegram.ext import ContextTypes&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR = Path("downloads").resolve()&lt;br&gt;
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)&lt;/p&gt;

&lt;p&gt;async def receive_photo(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.photo:
    return

photo = message.photo[-1]

telegram_file = await context.bot.get_file(photo.file_id)

filename = f"{uuid4().hex}.jpg"
destination = DOWNLOAD_DIR / filename

await telegram_file.download_to_drive(
    custom_path=destination
)

await message.reply_text(
    f"Photo received successfully.\nSaved as: {filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The handler performs four operations:&lt;/p&gt;

&lt;p&gt;Selects the largest photo version&lt;br&gt;
Retrieves its File object&lt;br&gt;
Generates a unique local filename&lt;br&gt;
Downloads the photo into the downloads directory&lt;/p&gt;

&lt;p&gt;The download_to_drive() documentation describes the available download behavior in python-telegram-bot.&lt;/p&gt;

&lt;p&gt;Receiving Documents Safely&lt;/p&gt;

&lt;p&gt;Documents require more validation because users can upload many different file formats.&lt;/p&gt;

&lt;p&gt;For this example, we will allow:&lt;/p&gt;

&lt;p&gt;PDF&lt;br&gt;
TXT&lt;br&gt;
CSV&lt;br&gt;
JSON&lt;br&gt;
ZIP&lt;/p&gt;

&lt;p&gt;We will also apply an application-level limit of 10 MB.&lt;/p&gt;

&lt;p&gt;MAX_FILE_SIZE = 10 * 1024 * 1024&lt;/p&gt;

&lt;p&gt;ALLOWED_EXTENSIONS = {&lt;br&gt;
    ".pdf",&lt;br&gt;
    ".txt",&lt;br&gt;
    ".csv",&lt;br&gt;
    ".json",&lt;br&gt;
    ".zip",&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now create the document handler:&lt;/p&gt;

&lt;p&gt;async def receive_document(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.document:
    return

document = message.document

if document.file_size and document.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This file is too large. The current limit is 10 MB."
    )
    return

original_name = Path(
    document.file_name or "upload.bin"
).name

extension = Path(original_name).suffix.lower()

if extension not in ALLOWED_EXTENSIONS:
    await message.reply_text(
        "Unsupported file type. Please send a PDF, TXT, CSV, JSON, or ZIP file."
    )
    return

safe_filename = f"{uuid4().hex}{extension}"
destination = DOWNLOAD_DIR / safe_filename

telegram_file = await context.bot.get_file(
    document.file_id
)

await telegram_file.download_to_drive(
    custom_path=destination
)

await message.reply_text(
    "Document received successfully.\n"
    f"Original name: {original_name}\n"
    f"Stored as: {safe_filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Why Generate a New Filename?&lt;/p&gt;

&lt;p&gt;Saving an uploaded document using its original name can create several problems.&lt;/p&gt;

&lt;p&gt;Two users might upload files with the same name:&lt;/p&gt;

&lt;p&gt;report.pdf&lt;/p&gt;

&lt;p&gt;A malicious filename might also contain path components intended to escape the download directory.&lt;/p&gt;

&lt;p&gt;Using this line removes path information:&lt;/p&gt;

&lt;p&gt;original_name = Path(document.file_name).name&lt;/p&gt;

&lt;p&gt;Generating a UUID-based filename also prevents accidental overwrites:&lt;/p&gt;

&lt;p&gt;safe_filename = f"{uuid4().hex}{extension}"&lt;/p&gt;

&lt;p&gt;Do not assume that a filename extension or MIME type proves that a file is safe. Both values can be misleading. A production application should inspect file contents and scan untrusted uploads before further processing.&lt;/p&gt;

&lt;p&gt;Complete Working Bot&lt;/p&gt;

&lt;p&gt;Here is the complete example:&lt;/p&gt;

&lt;p&gt;import logging&lt;br&gt;
import os&lt;br&gt;
from pathlib import Path&lt;br&gt;
from uuid import uuid4&lt;/p&gt;

&lt;p&gt;from telegram import Update&lt;br&gt;
from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
    MessageHandler,&lt;br&gt;
    filters,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logging.basicConfig(&lt;br&gt;
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",&lt;br&gt;
    level=logging.INFO,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR = Path("downloads").resolve()&lt;br&gt;
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)&lt;/p&gt;

&lt;p&gt;MAX_FILE_SIZE = 10 * 1024 * 1024&lt;/p&gt;

&lt;p&gt;ALLOWED_EXTENSIONS = {&lt;br&gt;
    ".pdf",&lt;br&gt;
    ".txt",&lt;br&gt;
    ".csv",&lt;br&gt;
    ".json",&lt;br&gt;
    ".zip",&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    await update.effective_message.reply_text(&lt;br&gt;
        "Send me a photo or an allowed document.\n"&lt;br&gt;
        "Supported documents: PDF, TXT, CSV, JSON, and ZIP."&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;async def receive_photo(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.photo:
    return

photo = message.photo[-1]

if photo.file_size and photo.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This photo is too large. The current limit is 10 MB."
    )
    return

telegram_file = await context.bot.get_file(
    photo.file_id
)

filename = f"{uuid4().hex}.jpg"
destination = DOWNLOAD_DIR / filename

await telegram_file.download_to_drive(
    custom_path=destination
)

logger.info(
    "Saved photo %s as %s",
    photo.file_unique_id,
    filename,
)

await message.reply_text(
    f"Photo downloaded successfully.\nFile ID: {filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def receive_document(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    message = update.effective_message&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not message or not message.document:
    return

document = message.document

if document.file_size and document.file_size &amp;gt; MAX_FILE_SIZE:
    await message.reply_text(
        "This document is too large. The current limit is 10 MB."
    )
    return

original_name = Path(
    document.file_name or "upload.bin"
).name

extension = Path(original_name).suffix.lower()

if extension not in ALLOWED_EXTENSIONS:
    await message.reply_text(
        "Unsupported file type.\n"
        "Allowed formats: PDF, TXT, CSV, JSON, and ZIP."
    )
    return

safe_filename = f"{uuid4().hex}{extension}"
destination = DOWNLOAD_DIR / safe_filename

telegram_file = await context.bot.get_file(
    document.file_id
)

await telegram_file.download_to_drive(
    custom_path=destination
)

logger.info(
    "Saved document %s as %s",
    document.file_unique_id,
    safe_filename,
)

await message.reply_text(
    "Document downloaded successfully.\n"
    f"Original name: {original_name}\n"
    f"File ID: {safe_filename}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def unsupported_file(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    await update.effective_message.reply_text(&lt;br&gt;
        "I cannot process this message type yet. "&lt;br&gt;
        "Please send a photo or a supported document."&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;async def error_handler(&lt;br&gt;
    update: object,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    logger.exception(&lt;br&gt;
        "An error occurred while processing an update",&lt;br&gt;
        exc_info=context.error,&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.getenv("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()

application.add_handler(
    CommandHandler("start", start)
)

application.add_handler(
    MessageHandler(filters.PHOTO, receive_photo)
)

application.add_handler(
    MessageHandler(
        filters.Document.ALL,
        receive_document,
    )
)

application.add_handler(
    MessageHandler(
        ~filters.COMMAND,
        unsupported_file,
    )
)

application.add_error_handler(error_handler)

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Run the bot:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Open your bot in Telegram, send /start, and then upload a photo or supported document.&lt;/p&gt;

&lt;p&gt;The downloaded files will appear in:&lt;/p&gt;

&lt;p&gt;downloads/&lt;br&gt;
file_id vs. file_unique_id&lt;/p&gt;

&lt;p&gt;These two values serve different purposes.&lt;/p&gt;

&lt;p&gt;file_id&lt;/p&gt;

&lt;p&gt;A file_id can be passed to Telegram API methods. Your bot can use it to retrieve or resend a previously uploaded file without uploading that file again.&lt;/p&gt;

&lt;p&gt;However, a file_id is associated with the bot that received it. It should not be treated as a universal public identifier.&lt;/p&gt;

&lt;p&gt;file_unique_id&lt;/p&gt;

&lt;p&gt;A file_unique_id helps identify the same file over time and across bots.&lt;/p&gt;

&lt;p&gt;It is useful for:&lt;/p&gt;

&lt;p&gt;Detecting duplicate uploads&lt;br&gt;
Creating database references&lt;br&gt;
Comparing previously received media&lt;/p&gt;

&lt;p&gt;It cannot be used directly to download or resend a file.&lt;/p&gt;

&lt;p&gt;A practical database record could contain:&lt;/p&gt;

&lt;p&gt;file_id&lt;br&gt;
file_unique_id&lt;br&gt;
original_name&lt;br&gt;
stored_name&lt;br&gt;
file_size&lt;br&gt;
uploaded_by&lt;br&gt;
uploaded_at&lt;br&gt;
Important Security Rules&lt;/p&gt;

&lt;p&gt;File-upload bots should be treated like public upload forms. Never trust uploaded content automatically.&lt;/p&gt;

&lt;p&gt;Apply these precautions:&lt;/p&gt;

&lt;p&gt;Set your own size limit. Reject files before downloading whenever file_size is available.&lt;br&gt;
Generate server-side filenames. Do not use raw user filenames as storage paths.&lt;br&gt;
Use an extension allowlist. Reject formats your application does not need.&lt;br&gt;
Inspect actual contents. File extensions and MIME types can be forged.&lt;br&gt;
Store uploads outside the public web root.&lt;br&gt;
Never execute uploaded files.&lt;br&gt;
Scan untrusted files before opening or processing them.&lt;br&gt;
Restrict access by user ID if the bot is intended for a private team.&lt;br&gt;
Avoid exposing local server paths in bot replies.&lt;br&gt;
Review Telegram’s current file limits before designing large-file workflows.&lt;/p&gt;

&lt;p&gt;The official MessageFilter documentation lists filters for photos, documents, MIME types, extensions, videos, audio, voice messages, and other Telegram content.&lt;/p&gt;

&lt;p&gt;Common Problems&lt;br&gt;
The Photo Handler Does Not Run&lt;/p&gt;

&lt;p&gt;Confirm that the photo handler is registered before a broad fallback handler:&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    MessageHandler(filters.PHOTO, receive_photo)&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Handler order matters because an earlier matching handler in the same group may process the update first.&lt;/p&gt;

&lt;p&gt;The Download Directory Is Missing&lt;/p&gt;

&lt;p&gt;Create it before starting the bot:&lt;/p&gt;

&lt;p&gt;DOWNLOAD_DIR.mkdir(&lt;br&gt;
    parents=True,&lt;br&gt;
    exist_ok=True&lt;br&gt;
)&lt;br&gt;
The Bot Rejects a Valid File&lt;/p&gt;

&lt;p&gt;Print or log the received filename and MIME type:&lt;/p&gt;

&lt;p&gt;logger.info(&lt;br&gt;
    "Received %s with MIME type %s",&lt;br&gt;
    document.file_name,&lt;br&gt;
    document.mime_type,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Then check whether its extension is included in ALLOWED_EXTENSIONS.&lt;/p&gt;

&lt;p&gt;Downloading Large Files Fails&lt;/p&gt;

&lt;p&gt;Telegram’s Bot API, the selected library, your network, and your hosting environment may impose different constraints. Check the current Bot API documentation and configure an application-level limit suitable for your server.&lt;/p&gt;

&lt;p&gt;Where to Go Next&lt;/p&gt;

&lt;p&gt;After receiving files successfully, you can extend the bot to:&lt;/p&gt;

&lt;p&gt;Upload files to cloud object storage&lt;br&gt;
Save metadata in PostgreSQL or SQLite&lt;br&gt;
Generate image thumbnails&lt;br&gt;
Extract text from PDF documents&lt;br&gt;
Scan uploads for malware&lt;br&gt;
Detect duplicate files with file_unique_id&lt;br&gt;
Restrict uploads to approved Telegram users&lt;br&gt;
Send processing results back to the user&lt;/p&gt;

&lt;p&gt;The core workflow remains the same: validate the incoming message, obtain its file_id, request the File object, and download it to a controlled destination.&lt;/p&gt;

&lt;p&gt;繁體中文摘要&lt;/p&gt;

&lt;p&gt;這篇教學示範如何使用 Python 與 python-telegram-bot 接收 Telegram 使用者傳送的照片及文件。程式會透過 file_id 取得檔案、限制檔案大小、檢查副檔名，並以 UUID 產生不重複的安全檔名。&lt;/p&gt;

&lt;p&gt;正式部署時，不應直接信任使用者提供的檔名、MIME 類型或副檔名。建議將上傳內容存放在網站公開目錄以外，加入檔案內容檢查、惡意程式掃描及使用者權限限制，避免上傳功能成為安全漏洞。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Add a Command Menu to a Telegram Bot with Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:07:55 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-add-a-command-menu-to-a-telegram-bot-with-python-990</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-add-a-command-menu-to-a-telegram-bot-with-python-990</guid>
      <description>&lt;p&gt;A Telegram bot can recognize commands without displaying them in the client. However, users should not have to memorize every available command. Registering a command menu lets Telegram show a list when someone taps the menu button or types &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will create &lt;code&gt;/start&lt;/code&gt;, &lt;code&gt;/help&lt;/code&gt;, and &lt;code&gt;/status&lt;/code&gt; handlers, register them through the Bot API, and display different command menus in private chats and groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Telegram Bot Command Menu?
&lt;/h2&gt;

&lt;p&gt;A bot command begins with a slash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A registered command has two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt;: the value users send, without the leading slash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: a short explanation shown in the Telegram interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start - Start the bot
help - Show available commands
status - Check the service status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering this list improves discoverability, but it does not create the command logic. Your Python application must still contain a handler for every command.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;/status&lt;/code&gt; appears in the menu but no &lt;code&gt;CommandHandler&lt;/code&gt; exists for it, Telegram will send the command to the bot, but the program will not know how to respond.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10 or newer&lt;/li&gt;
&lt;li&gt;A Telegram bot created through BotFather&lt;/li&gt;
&lt;li&gt;A valid bot token&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;python-telegram-bot&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;Basic knowledge of Python functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the Telegram client is not installed on your test device, follow this &lt;a href="https://telegram-com.com/" rel="noopener noreferrer"&gt;Telegram download guide&lt;/a&gt; before starting.&lt;/p&gt;

&lt;p&gt;Create a project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;telegram-command-menu
&lt;span class="nb"&gt;cd &lt;/span&gt;telegram-command-menu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it on macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it in Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="n"&gt;\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; python-telegram-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the bot token in an environment variable.&lt;/p&gt;

&lt;p&gt;macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PASTE_YOUR_TOKEN_HERE'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PASTE_YOUR_TOKEN_HERE"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not place a real token directly in a public code sample or repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Command Handlers
&lt;/h2&gt;

&lt;p&gt;Create a file named &lt;code&gt;bot.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Use /help to view available commands.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Available commands:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start - Start the bot&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/help - Show this help message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status - Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢 The service is operating normally.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the bot and test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handlers work, but the Telegram command menu may still be empty. The next step is to register the commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Register Commands with BotFather
&lt;/h2&gt;

&lt;p&gt;For a small bot with a fixed command list, BotFather provides a simple setup method.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;@BotFather&lt;/code&gt; and send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mybots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Bot
→ Edit Bot
→ Edit Commands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Submit one command per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start - Start the bot
help - Show available commands
status - Check the service status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving, reopen the bot or type &lt;code&gt;/&lt;/code&gt; in the message field. Telegram should display the menu.&lt;/p&gt;

&lt;p&gt;This approach is convenient when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The command list rarely changes.&lt;/li&gt;
&lt;li&gt;The bot has only one language.&lt;/li&gt;
&lt;li&gt;Every chat uses the same commands.&lt;/li&gt;
&lt;li&gt;Command registration does not need to be part of deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a larger project, registering commands from Python keeps the visible menu synchronized with the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Register Commands from Python
&lt;/h2&gt;

&lt;p&gt;Import &lt;code&gt;BotCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a startup function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the application builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;post_init&lt;/code&gt; callback runs after the application has been initialized and can access &lt;code&gt;application.bot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The relevant section now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check the service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command menu is now registered automatically whenever the application starts.&lt;/p&gt;

&lt;p&gt;Telegram documents this operation in the official &lt;a href="https://core.telegram.org/bots/api#setmycommands" rel="noopener noreferrer"&gt;&lt;code&gt;setMyCommands&lt;/code&gt; reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Naming Rules
&lt;/h2&gt;

&lt;p&gt;A command name should be simple and predictable.&lt;/p&gt;

&lt;p&gt;Good examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start
help
status
account
language
server_1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start
show-status
my command
/settings!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bot commands use lowercase English letters, digits, and underscores. Do not include the leading slash when creating a &lt;code&gt;BotCommand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incorrect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Descriptions should explain the result of using the command. Avoid vague descriptions such as “Click here” or “Do something.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Different Menus for Private Chats and Groups
&lt;/h2&gt;

&lt;p&gt;A bot may need one menu in private chats and another in groups.&lt;/p&gt;

&lt;p&gt;Private commands might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/start
/help
/settings
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Group commands might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/help
/rules
/report
/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the required scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;set_commands()&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;private_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open personal settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;group_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show group commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Display the group rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Report a message to moderators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check bot status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;private_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;group_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visible menu now depends on the chat type.&lt;/p&gt;

&lt;p&gt;However, the menu and the handlers remain separate. If &lt;code&gt;/rules&lt;/code&gt; is displayed for groups, a handler must also be registered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Telegram supports several command scopes, including default commands, all private chats, all group chats, administrators, specific chats, and specific chat members. The selection rules are documented in the official &lt;a href="https://core.telegram.org/bots/api#botcommandscope" rel="noopener noreferrer"&gt;BotCommandScope reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Group-Only Command Handlers
&lt;/h2&gt;

&lt;p&gt;A command visible in a group menu should normally reject private-chat usage.&lt;/p&gt;

&lt;p&gt;Import &lt;code&gt;ChatType&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;/rules&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The /rules command is available in groups only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Group rules:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1. Be respectful.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2. Stay on topic.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3. Do not post sensitive information.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hiding a command from the private-chat menu improves the interface, but it does not enforce authorization. Users may still type a command manually, so the handler must validate the chat type and permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Administrator-Only Commands
&lt;/h2&gt;

&lt;p&gt;Some commands should only be visible to group administrators.&lt;/p&gt;

&lt;p&gt;Import the administrator scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BotCommandScopeAllChatAdministrators&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register administrator commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;admin_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review reported messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cleanup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Remove recent unwanted messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;admin_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllChatAdministrators&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The menu scope only controls visibility. It does not prove that the caller is authorized.&lt;/p&gt;

&lt;p&gt;A sensitive handler must still check the member status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_group_admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_chat_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;administrator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;creator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the check inside an administrator command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;is_group_admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You do not have permission to use this command.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cleanup request accepted.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never rely on command visibility as an access-control mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Language-Specific Commands
&lt;/h2&gt;

&lt;p&gt;Telegram can register different descriptions for different language codes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;english_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;traditional_chinese_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;開始使用機器人&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;查看可用指令&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the default menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;english_commands&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register Traditional Chinese descriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;traditional_chinese_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zh-hant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A default command list should still exist for users whose language does not match a localized menu.&lt;/p&gt;

&lt;p&gt;The language code affects menu descriptions, not the command handler itself. &lt;code&gt;/help&lt;/code&gt; can inspect &lt;code&gt;update.effective_user.language_code&lt;/code&gt; if the bot needs to respond in a different language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Working Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s - %(name)s - &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(levelname)s - %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Use /help to view available commands.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Available commands:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start - Start the bot&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/help - Show this message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/status - Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢 The service is operating normally.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_chat&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ChatType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PRIVATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The /rules command is available in groups only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Group rules:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1. Be respectful.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2. Stay on topic.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3. Do not share sensitive information.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;private_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start the bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show available commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check service status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;group_commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Show group commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Display the group rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;BotCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check bot status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;private_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllPrivateChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_my_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;group_commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BotCommandScopeAllGroupChats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;error_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContextTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An exception occurred while processing an update.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The TELEGRAM_BOT_TOKEN environment variable is missing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_error_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python bot.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the bot in both a private chat and a group. The displayed commands should differ according to the selected scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Commands Work but the Menu Is Empty
&lt;/h3&gt;

&lt;p&gt;The handlers exist, but the command list has not been registered. Use BotFather or &lt;code&gt;set_my_commands()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After registering commands, close and reopen the chat if the client does not refresh immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Command Appears but Does Nothing
&lt;/h3&gt;

&lt;p&gt;The command was registered in the menu, but no matching handler exists.&lt;/p&gt;

&lt;p&gt;For every visible command, confirm that the application contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler_function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Old Commands Still Appear
&lt;/h3&gt;

&lt;p&gt;The application may be registering commands under a different scope. It is also possible that commands were previously set through BotFather or another deployment.&lt;/p&gt;

&lt;p&gt;Inspect which scopes are being used, update them consistently, and restart the client if necessary.&lt;/p&gt;

&lt;p&gt;Telegram also provides &lt;code&gt;deleteMyCommands&lt;/code&gt; for removing a command list from a selected scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Group Commands Appear in Private Chats
&lt;/h3&gt;

&lt;p&gt;A default command list may be overriding the intended design, or the private scope was not configured.&lt;/p&gt;

&lt;p&gt;Register separate &lt;code&gt;BotCommandScopeAllPrivateChats&lt;/code&gt; and &lt;code&gt;BotCommandScopeAllGroupChats&lt;/code&gt; lists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Administrator Commands Are Visible to the Wrong Users
&lt;/h3&gt;

&lt;p&gt;Review the command scope, but also remember that visibility is not authorization. Every sensitive handler must verify the caller's current member status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands Stop Updating After Code Changes
&lt;/h3&gt;

&lt;p&gt;The startup callback may not be running, or the application may be exiting before &lt;code&gt;set_my_commands()&lt;/code&gt; succeeds.&lt;/p&gt;

&lt;p&gt;Add logging inside the startup function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Registering bot commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Register commands here.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not log the bot token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Design Guidelines
&lt;/h2&gt;

&lt;p&gt;A useful command menu should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short enough to scan quickly&lt;/li&gt;
&lt;li&gt;Consistent with implemented handlers&lt;/li&gt;
&lt;li&gt;Different where private and group workflows differ&lt;/li&gt;
&lt;li&gt;Localized when the bot serves multiple languages&lt;/li&gt;
&lt;li&gt;Protected by server-side permission checks&lt;/li&gt;
&lt;li&gt;Updated as part of deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid registering every internal or debugging command. Public menus should contain only actions that ordinary users need.&lt;/p&gt;

&lt;p&gt;The command menu improves discoverability, while handlers provide behavior and authorization. Both parts must be maintained together.&lt;/p&gt;

&lt;h2&gt;
  
  
  繁體中文摘要
&lt;/h2&gt;

&lt;p&gt;Telegram Bot 的指令選單可以讓使用者在輸入 &lt;code&gt;/&lt;/code&gt; 時看到 &lt;code&gt;/start&lt;/code&gt;、&lt;code&gt;/help&lt;/code&gt; 與 &lt;code&gt;/status&lt;/code&gt; 等功能。指令可以透過 BotFather 手動設定，也可以使用 &lt;code&gt;set_my_commands()&lt;/code&gt; 在 Python 程式啟動時自動註冊。&lt;/p&gt;

&lt;p&gt;私人聊天、群組及管理員可以使用不同的 BotCommandScope。不過，選單只控制顯示內容，不代表使用者具有操作權限。管理功能仍需在處理函式中驗證聊天類型與管理員身分。&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This tutorial was drafted with AI assistance and reviewed against the Telegram Bot API and python-telegram-bot documentation. Test all permission-sensitive commands with a development bot before production use.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build a Telegram Bot Inline Keyboard with Python</title>
      <dc:creator>Telegram Bot Engineering Note</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:01:40 +0000</pubDate>
      <link>https://dev.to/telegram_bot_notes/how-to-build-a-telegram-bot-inline-keyboard-with-python-463o</link>
      <guid>https://dev.to/telegram_bot_notes/how-to-build-a-telegram-bot-inline-keyboard-with-python-463o</guid>
      <description>&lt;p&gt;Text commands are enough for a basic Telegram bot, but they become inconvenient as the number of features grows. An inline keyboard gives users a clearer interface by placing buttons directly below a message.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a small menu using Python and python-telegram-bot. The bot will display buttons, process callback queries, update the original message, and safely handle unknown actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1uis3o0ceywmdlcp6qt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1uis3o0ceywmdlcp6qt.jpg" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What Is an Inline Keyboard?&lt;/p&gt;

&lt;p&gt;An inline keyboard is a collection of buttons attached to a Telegram message. Unlike a reply keyboard, it does not replace the user's typing area.&lt;/p&gt;

&lt;p&gt;Inline keyboards are useful for:&lt;/p&gt;

&lt;p&gt;Navigation menus&lt;br&gt;
Confirmation dialogs&lt;br&gt;
Pagination&lt;br&gt;
Language selection&lt;br&gt;
Account settings&lt;br&gt;
Order and service status checks&lt;br&gt;
Links to websites or documentation&lt;br&gt;
Group and channel administration&lt;/p&gt;

&lt;p&gt;Telegram represents an inline keyboard with two main objects:&lt;/p&gt;

&lt;p&gt;InlineKeyboardMarkup contains the complete button layout.&lt;br&gt;
InlineKeyboardButton represents an individual button.&lt;/p&gt;

&lt;p&gt;A keyboard is arranged as a list of rows. Each row contains one or more buttons.&lt;/p&gt;

&lt;p&gt;[ Documentation ] [ Service Status ]&lt;br&gt;
[        Open the Bot API Docs        ]&lt;/p&gt;

&lt;p&gt;A simplified API representation looks like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "inline_keyboard": [&lt;br&gt;
    [&lt;br&gt;
      {&lt;br&gt;
        "text": "Documentation",&lt;br&gt;
        "callback_data": "menu:docs"&lt;br&gt;
      },&lt;br&gt;
      {&lt;br&gt;
        "text": "Service Status",&lt;br&gt;
        "callback_data": "menu:status"&lt;br&gt;
      }&lt;br&gt;
    ],&lt;br&gt;
    [&lt;br&gt;
      {&lt;br&gt;
        "text": "Open the Bot API Docs",&lt;br&gt;
        "url": "&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;"&lt;br&gt;
      }&lt;br&gt;
    ]&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A button with callback_data sends a callback query to the bot. A button with a url opens the destination directly and does not trigger the same callback flow.&lt;/p&gt;

&lt;p&gt;See Telegram's official InlineKeyboardMarkup documentation for the complete object definition.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;/p&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;p&gt;Python 3.10 or newer&lt;br&gt;
A Telegram account&lt;br&gt;
A bot created through &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;&lt;br&gt;
The bot token stored securely&lt;br&gt;
Basic familiarity with running Python from a terminal&lt;/p&gt;

&lt;p&gt;If you still need the client, use this Telegram download guide to install it on desktop or mobile.&lt;/p&gt;

&lt;p&gt;This tutorial uses the asynchronous python-telegram-bot interface.&lt;/p&gt;

&lt;p&gt;Create the Project&lt;/p&gt;

&lt;p&gt;Create a new directory:&lt;/p&gt;

&lt;p&gt;mkdir telegram-inline-keyboard&lt;br&gt;
cd telegram-inline-keyboard&lt;/p&gt;

&lt;p&gt;Create a virtual environment:&lt;/p&gt;

&lt;p&gt;python -m venv .venv&lt;/p&gt;

&lt;p&gt;Activate it on macOS or Linux:&lt;/p&gt;

&lt;p&gt;source .venv/bin/activate&lt;/p&gt;

&lt;p&gt;Activate it in Windows PowerShell:&lt;/p&gt;

&lt;p&gt;.venv\Scripts\Activate.ps1&lt;/p&gt;

&lt;p&gt;Install or upgrade the library:&lt;/p&gt;

&lt;p&gt;python -m pip install --upgrade python-telegram-bot&lt;/p&gt;

&lt;p&gt;The current stable python-telegram-bot library uses an asynchronous API. Handler functions therefore use async def, and Telegram operations must be awaited.&lt;/p&gt;

&lt;p&gt;Store the Bot Token Safely&lt;/p&gt;

&lt;p&gt;Do not place a real token directly in source code.&lt;/p&gt;

&lt;p&gt;On macOS or Linux:&lt;/p&gt;

&lt;p&gt;export TELEGRAM_BOT_TOKEN='PASTE_YOUR_TOKEN_HERE'&lt;/p&gt;

&lt;p&gt;In Windows PowerShell:&lt;/p&gt;

&lt;p&gt;$env:TELEGRAM_BOT_TOKEN="PASTE_YOUR_TOKEN_HERE"&lt;/p&gt;

&lt;p&gt;The environment variable exists only in the current terminal session. For production, use the secret-management feature provided by your hosting platform.&lt;/p&gt;

&lt;p&gt;Never commit a .env file containing a token to a public repository. If a token is exposed, revoke it through BotFather and generate a new one.&lt;/p&gt;

&lt;p&gt;Build the First Menu&lt;/p&gt;

&lt;p&gt;Create a file named bot.py:&lt;/p&gt;

&lt;p&gt;import os&lt;/p&gt;

&lt;p&gt;from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update&lt;br&gt;
from telegram.ext import Application, CommandHandler, ContextTypes&lt;/p&gt;

&lt;p&gt;def build_home_keyboard() -&amp;gt; InlineKeyboardMarkup:&lt;br&gt;
    keyboard = [&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "📚 Documentation",&lt;br&gt;
                callback_data="menu:docs",&lt;br&gt;
            ),&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🟢 Service Status",&lt;br&gt;
                callback_data="menu:status",&lt;br&gt;
            ),&lt;br&gt;
        ],&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🌐 Open the Bot API Docs",&lt;br&gt;
                url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
            )&lt;br&gt;
        ],&lt;br&gt;
    ]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return InlineKeyboardMarkup(keyboard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    if update.message is None:&lt;br&gt;
        return&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await update.message.reply_text(
    "Choose an option:",
    reply_markup=build_home_keyboard(),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.environ.get("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()
application.add_handler(CommandHandler("start", start))

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Run the bot:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Open the bot in Telegram and send:&lt;/p&gt;

&lt;p&gt;/start&lt;/p&gt;

&lt;p&gt;You should see two callback buttons and one URL button. The URL button already works, but the callback buttons do not yet have a handler.&lt;/p&gt;

&lt;p&gt;Understanding callback_data&lt;/p&gt;

&lt;p&gt;The callback_data value is a short string associated with a button:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Service Status",&lt;br&gt;
    callback_data="menu:status",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;When a user presses this button, Telegram sends a CallbackQuery update containing:&lt;/p&gt;

&lt;p&gt;menu:status&lt;/p&gt;

&lt;p&gt;A useful convention is to organize callback values with prefixes:&lt;/p&gt;

&lt;p&gt;menu:home&lt;br&gt;
menu:docs&lt;br&gt;
menu:status&lt;br&gt;
settings:language:en&lt;br&gt;
page:articles:2&lt;br&gt;
order:confirm:5821&lt;/p&gt;

&lt;p&gt;Telegram limits callback_data to a small payload, so it should contain an action identifier rather than a complete data object.&lt;/p&gt;

&lt;p&gt;Do not include the following information in callback data:&lt;/p&gt;

&lt;p&gt;Bot tokens&lt;br&gt;
Passwords&lt;br&gt;
API keys&lt;br&gt;
Payment information&lt;br&gt;
Email addresses&lt;br&gt;
Private user data&lt;br&gt;
Complete JSON documents&lt;br&gt;
Authorization credentials&lt;/p&gt;

&lt;p&gt;For database-backed actions, include a short record identifier and load the actual data on the server after validating the request.&lt;/p&gt;

&lt;p&gt;Add a Callback Query Handler&lt;/p&gt;

&lt;p&gt;Update the imports:&lt;/p&gt;

&lt;p&gt;from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CallbackQueryHandler,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Add the following function below start():&lt;/p&gt;

&lt;p&gt;async def handle_button(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    query = update.callback_query&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if query is None:
    return

action = query.data

if action == "menu:docs":
    await query.answer()

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                )
            ]
        ]
    )

    await query.edit_message_text(
        text=(
            "📚 Documentation\n\n"
            "Use the official Bot API reference to review "
            "available methods, objects, and update types."
        ),
        reply_markup=keyboard,
    )

elif action == "menu:status":
    await query.answer("Status refreshed")

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "🔄 Refresh",
                    callback_data="menu:status",
                ),
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                ),
            ]
        ]
    )

    await query.edit_message_text(
        text="🟢 The service is operating normally.",
        reply_markup=keyboard,
    )

elif action == "menu:home":
    await query.answer()

    await query.edit_message_text(
        text="Choose an option:",
        reply_markup=build_home_keyboard(),
    )

else:
    await query.answer(
        text="Unknown action",
        show_alert=True,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Register the callback handler in main():&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_button,&lt;br&gt;
        pattern=r"^menu:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The handler section should now look like this:&lt;/p&gt;

&lt;p&gt;application.add_handler(CommandHandler("start", start))&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_button,&lt;br&gt;
        pattern=r"^menu:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The regular expression limits this handler to callback values beginning with menu:. Future features can use separate prefixes and handlers.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;application.add_handler(&lt;br&gt;
    CallbackQueryHandler(&lt;br&gt;
        handle_settings,&lt;br&gt;
        pattern=r"^settings:",&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This keeps a larger bot easier to maintain.&lt;/p&gt;

&lt;p&gt;Why query.answer() Is Required&lt;/p&gt;

&lt;p&gt;After a user presses an inline button, the Telegram client displays a loading indicator. The bot should answer the callback query even if no notification needs to be shown.&lt;/p&gt;

&lt;p&gt;await query.answer()&lt;/p&gt;

&lt;p&gt;Without this call, the loading indicator may continue spinning and make the interface appear broken.&lt;/p&gt;

&lt;p&gt;To show a short notification:&lt;/p&gt;

&lt;p&gt;await query.answer("Settings saved")&lt;/p&gt;

&lt;p&gt;To display an alert dialog:&lt;/p&gt;

&lt;p&gt;await query.answer(&lt;br&gt;
    text="You do not have permission to perform this action.",&lt;br&gt;
    show_alert=True,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Only answer each callback once. Decide whether the response should be silent, a short notification, or an alert.&lt;/p&gt;

&lt;p&gt;Telegram explains this behavior in the official CallbackQuery documentation.&lt;/p&gt;

&lt;p&gt;Complete Working Example&lt;/p&gt;

&lt;p&gt;The following version includes logging, menu navigation, filtered callback handling, and a basic error handler:&lt;/p&gt;

&lt;p&gt;import logging&lt;br&gt;
import os&lt;/p&gt;

&lt;p&gt;from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update&lt;br&gt;
from telegram.ext import (&lt;br&gt;
    Application,&lt;br&gt;
    CallbackQueryHandler,&lt;br&gt;
    CommandHandler,&lt;br&gt;
    ContextTypes,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logging.basicConfig(&lt;br&gt;
    format=(&lt;br&gt;
        "%(asctime)s - %(name)s - "&lt;br&gt;
        "%(levelname)s - %(message)s"&lt;br&gt;
    ),&lt;br&gt;
    level=logging.INFO,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;def build_home_keyboard() -&amp;gt; InlineKeyboardMarkup:&lt;br&gt;
    keyboard = [&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "📚 Documentation",&lt;br&gt;
                callback_data="menu:docs",&lt;br&gt;
            ),&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🟢 Service Status",&lt;br&gt;
                callback_data="menu:status",&lt;br&gt;
            ),&lt;br&gt;
        ],&lt;br&gt;
        [&lt;br&gt;
            InlineKeyboardButton(&lt;br&gt;
                "🌐 Open the Bot API Docs",&lt;br&gt;
                url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
            )&lt;br&gt;
        ],&lt;br&gt;
    ]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return InlineKeyboardMarkup(keyboard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def start(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    if update.message is None:&lt;br&gt;
        return&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await update.message.reply_text(
    "Choose an option:",
    reply_markup=build_home_keyboard(),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def handle_button(&lt;br&gt;
    update: Update,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    query = update.callback_query&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if query is None:
    return

action = query.data

if action == "menu:docs":
    await query.answer()

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                )
            ]
        ]
    )

    await query.edit_message_text(
        text=(
            "📚 Documentation\n\n"
            "Review the Telegram Bot API reference "
            "for available methods and objects."
        ),
        reply_markup=keyboard,
    )

elif action == "menu:status":
    await query.answer("Status refreshed")

    keyboard = InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(
                    "🔄 Refresh",
                    callback_data="menu:status",
                ),
                InlineKeyboardButton(
                    "⬅️ Back",
                    callback_data="menu:home",
                ),
            ]
        ]
    )

    await query.edit_message_text(
        text="🟢 The service is operating normally.",
        reply_markup=keyboard,
    )

elif action == "menu:home":
    await query.answer()

    await query.edit_message_text(
        text="Choose an option:",
        reply_markup=build_home_keyboard(),
    )

else:
    await query.answer(
        text="Unknown action",
        show_alert=True,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;async def error_handler(&lt;br&gt;
    update: object,&lt;br&gt;
    context: ContextTypes.DEFAULT_TYPE,&lt;br&gt;
) -&amp;gt; None:&lt;br&gt;
    logger.error(&lt;br&gt;
        "An exception occurred while processing an update.",&lt;br&gt;
        exc_info=context.error,&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;def main() -&amp;gt; None:&lt;br&gt;
    token = os.environ.get("TELEGRAM_BOT_TOKEN")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not token:
    raise RuntimeError(
        "The TELEGRAM_BOT_TOKEN environment variable is missing."
    )

application = Application.builder().token(token).build()

application.add_handler(CommandHandler("start", start))
application.add_handler(
    CallbackQueryHandler(
        handle_button,
        pattern=r"^menu:",
    )
)
application.add_error_handler(error_handler)

application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

&lt;p&gt;Restart the program after saving:&lt;/p&gt;

&lt;p&gt;python bot.py&lt;/p&gt;

&lt;p&gt;Send /start again and test every button.&lt;/p&gt;

&lt;p&gt;The python-telegram-bot project also provides an official inline keyboard example that is useful when comparing implementation patterns.&lt;/p&gt;

&lt;p&gt;URL Buttons vs. Callback Buttons&lt;/p&gt;

&lt;p&gt;A URL button opens a destination:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Open Documentation",&lt;br&gt;
    url="&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;https://core.telegram.org/bots/api&lt;/a&gt;",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;A callback button sends data to the bot:&lt;/p&gt;

&lt;p&gt;InlineKeyboardButton(&lt;br&gt;
    "Check Status",&lt;br&gt;
    callback_data="menu:status",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Use a URL button when the only goal is navigation. Use a callback button when the server needs to validate an action, query a database, modify a message, or change the menu.&lt;/p&gt;

&lt;p&gt;Do not use callback data as a substitute for server-side authorization. A user interface is not a security boundary.&lt;/p&gt;

&lt;p&gt;Editing a Message vs. Sending a New One&lt;/p&gt;

&lt;p&gt;A menu usually works best when it edits the existing message:&lt;/p&gt;

&lt;p&gt;await query.edit_message_text(&lt;br&gt;
    text="Updated menu content"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This prevents a long trail of nearly identical menu messages.&lt;/p&gt;

&lt;p&gt;For permanent results, notifications, receipts, or audit information, send a new message instead:&lt;/p&gt;

&lt;p&gt;await query.message.reply_text(&lt;br&gt;
    "Your request has been completed."&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Choose based on whether the user needs to retain the previous result.&lt;/p&gt;

&lt;p&gt;Common Problems&lt;br&gt;
The Button Keeps Loading&lt;/p&gt;

&lt;p&gt;Confirm that every callback path calls one of the following:&lt;/p&gt;

&lt;p&gt;await query.answer()&lt;br&gt;
await query.answer("Completed")&lt;br&gt;
await query.answer(&lt;br&gt;
    "Permission denied",&lt;br&gt;
    show_alert=True,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Unknown and error paths also need a callback response.&lt;/p&gt;

&lt;p&gt;Nothing Happens After a Click&lt;/p&gt;

&lt;p&gt;Check that:&lt;/p&gt;

&lt;p&gt;CallbackQueryHandler is registered.&lt;br&gt;
The callback value matches the handler pattern.&lt;br&gt;
The bot process is still running.&lt;br&gt;
The token belongs to the correct bot.&lt;br&gt;
Another process is not consuming updates.&lt;br&gt;
A webhook is not conflicting with polling.&lt;br&gt;
The terminal does not contain an exception.&lt;br&gt;
Message Is Not Modified&lt;/p&gt;

&lt;p&gt;Telegram returns this error when the new text and keyboard are identical to the current message.&lt;/p&gt;

&lt;p&gt;A refresh button should either produce changed content or handle this case without treating it as a fatal error.&lt;/p&gt;

&lt;p&gt;A User Clicks an Old Button&lt;/p&gt;

&lt;p&gt;Inline buttons can remain visible in chat history. A user may press one after the referenced database record has expired or changed.&lt;/p&gt;

&lt;p&gt;Always verify:&lt;/p&gt;

&lt;p&gt;The user is still authorized.&lt;br&gt;
The record still exists.&lt;br&gt;
The action is still valid.&lt;br&gt;
The operation has not already been completed.&lt;br&gt;
The callback belongs to the expected workflow.&lt;/p&gt;

&lt;p&gt;Return a clear alert if the action is no longer available.&lt;/p&gt;

&lt;p&gt;Duplicate Clicks Create Duplicate Operations&lt;/p&gt;

&lt;p&gt;For sensitive actions such as payments, deletions, or order creation, add server-side protection:&lt;/p&gt;

&lt;p&gt;Idempotency keys&lt;br&gt;
Transaction checks&lt;br&gt;
Permission validation&lt;br&gt;
Current-state validation&lt;br&gt;
Rate limiting&lt;br&gt;
Audit logs&lt;br&gt;
Confirmation steps&lt;/p&gt;

&lt;p&gt;Disabling or replacing a button improves the interface, but it does not replace backend safeguards.&lt;/p&gt;

&lt;p&gt;A Better Project Structure&lt;/p&gt;

&lt;p&gt;A single file is acceptable for this tutorial. A larger bot should separate menu creation, callback routing, and business logic.&lt;/p&gt;

&lt;p&gt;telegram-inline-keyboard/&lt;br&gt;
├── bot.py&lt;br&gt;
├── handlers/&lt;br&gt;
│   ├── commands.py&lt;br&gt;
│   └── callbacks.py&lt;br&gt;
├── keyboards/&lt;br&gt;
│   └── main_menu.py&lt;br&gt;
├── services/&lt;br&gt;
│   └── status_service.py&lt;br&gt;
└── requirements.txt&lt;/p&gt;

&lt;p&gt;This structure makes it easier to test functions and add new menus without turning bot.py into a large collection of unrelated conditions.&lt;/p&gt;

&lt;p&gt;Where to Go Next&lt;/p&gt;

&lt;p&gt;Once the basic menu works, useful extensions include:&lt;/p&gt;

&lt;p&gt;Paginated article lists&lt;br&gt;
Language settings&lt;br&gt;
Confirmation dialogs&lt;br&gt;
Database-backed menus&lt;br&gt;
Role-based administrator buttons&lt;br&gt;
Image and document actions&lt;br&gt;
Webhook deployment&lt;br&gt;
Persistent user preferences&lt;br&gt;
Rate limiting and audit logs&lt;/p&gt;

&lt;p&gt;The core workflow remains the same:&lt;/p&gt;

&lt;p&gt;Create an InlineKeyboardButton&lt;br&gt;
→ assign callback_data&lt;br&gt;
→ register CallbackQueryHandler&lt;br&gt;
→ validate query.data&lt;br&gt;
→ call query.answer()&lt;br&gt;
→ update or send a message&lt;/p&gt;

&lt;p&gt;Once this pattern is clear, a command-only Telegram bot can be turned into a practical interactive application.&lt;/p&gt;

&lt;p&gt;繁體中文摘要&lt;/p&gt;

&lt;p&gt;Inline Keyboard 可以在 Telegram Bot 訊息下方加入互動按鈕。使用者點擊含有 callback_data 的按鈕後，程式會收到 Callback Query。處理函式必須驗證資料並執行 query.answer()，否則按鈕可能持續顯示載入狀態。&lt;/p&gt;

&lt;p&gt;一般選單適合使用 edit_message_text() 更新原訊息；需要保留的操作結果則可以另外傳送新訊息。涉及付款、刪除或管理權限時，仍需在伺服器端進行身分與狀態驗證，不能只依賴按鈕介面。&lt;/p&gt;

&lt;p&gt;Disclosure: This tutorial was drafted with AI assistance and reviewed against the Telegram Bot API and python-telegram-bot documentation. Test the code in a development bot before using it in production.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
