<?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: Sly Bye</title>
    <description>The latest articles on DEV Community by Sly Bye (@slugbye).</description>
    <link>https://dev.to/slugbye</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%2F4036005%2F34349aef-6b12-46aa-9438-3745eccd47a1.jpg</url>
      <title>DEV Community: Sly Bye</title>
      <link>https://dev.to/slugbye</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slugbye"/>
    <language>en</language>
    <item>
      <title>Your link checker thinks deleted Telegram bots are alive</title>
      <dc:creator>Sly Bye</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:11:51 +0000</pubDate>
      <link>https://dev.to/slugbye/your-link-checker-thinks-deleted-telegram-bots-are-alive-3073</link>
      <guid>https://dev.to/slugbye/your-link-checker-thinks-deleted-telegram-bots-are-alive-3073</guid>
      <description>&lt;p&gt;Delete a Telegram bot and &lt;code&gt;https://t.me/your_deleted_bot&lt;/code&gt; keeps returning &lt;strong&gt;HTTP 200&lt;/strong&gt;&lt;br&gt;
with a page that looks completely normal. Every link checker I know of — CI actions,&lt;br&gt;
directory scripts, monitoring cron jobs — reports it as healthy forever.&lt;/p&gt;

&lt;p&gt;If you maintain anything that lists Telegram bots, some fraction of your list is&lt;br&gt;
already dead and your checks are telling you it is fine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reproducing it
&lt;/h2&gt;

&lt;p&gt;Pick a username that has never existed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://t.me/nonexistent_test_bot_77712
&lt;span class="c"&gt;# 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hundred. No redirect, no 404, no soft-404 marker in the body that a status check&lt;br&gt;
would catch.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where the truth is
&lt;/h2&gt;

&lt;p&gt;The status code is useless here, but the Open Graph title is not. I measured four&lt;br&gt;
usernames — two live bots, two that do not exist:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;t.me/BookClassBot&lt;/code&gt; (live)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BookClass&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;t.me/instanavy_bot&lt;/code&gt; (live)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StoryViewer - anonymous instagram story viewer tool&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t.me/nonexistent_test_bot_77712&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Telegram: Contact @nonexistent_test_bot_77712&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t.me/zzz_definitely_not_a_real_bot_9182&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Telegram – a new era of messaging&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A live bot puts its own display name in &lt;code&gt;og:title&lt;/code&gt;. A dead one gets one of two&lt;br&gt;
Telegram placeholders: &lt;code&gt;Telegram: Contact @&amp;lt;username&amp;gt;&lt;/code&gt;, or — if the username is not&lt;br&gt;
even syntactically valid — the generic &lt;code&gt;Telegram – a new era of messaging&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole signal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://t.me/some_bot | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;meta property="og:title" content="[^"]*"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The check
&lt;/h2&gt;

&lt;p&gt;Standard library only, no dependencies:&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;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="n"&gt;UA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (compatible; linkcheck/1.0)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;DEAD_EXACT&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;Telegram – a new era of messaging&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;Telegram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;DEAD_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Telegram: Contact @&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;telegram_bot_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;True if the bot behind a t.me URL still exists.

    HTTP status is not usable here: Telegram serves 200 with a placeholder page
    for usernames that were deleted or never existed. The Open Graph title is
    what actually differs.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&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;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UA&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200_000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&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;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;meta\s+property=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;og:title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s+content=&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="s"&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;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="nf"&gt;strip&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;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&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;title&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DEAD_EXACT&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEAD_PREFIX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the em dash in &lt;code&gt;Telegram – a new era of messaging&lt;/code&gt;. It is U+2013, not a hyphen.&lt;br&gt;
I lost a few minutes to that one.&lt;/p&gt;
&lt;h2&gt;
  
  
  It survives the URL variants
&lt;/h2&gt;

&lt;p&gt;Mini Apps get linked in several shapes, and the check holds for all of them — the&lt;br&gt;
title still resolves to the app's real name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t.me/DefyTONBot/app                            -&amp;gt; "DefyTON"
t.me/arena_defense_bot/arena_defense           -&amp;gt; "ARENA DEFENSE"
t.me/lexicon_snap_bot?startapp=cat_awesome     -&amp;gt; "LexiCon"
t.me/playful_mind_bot/play?startapp=cat_awesome_tma -&amp;gt; "Playful Mind"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can point the check at whatever URL someone submitted, without normalising it&lt;br&gt;
to the bare username first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;Because directories rot silently, and silent rot is the thing that kills them.&lt;/p&gt;

&lt;p&gt;A list of bots that returns 200 across the board looks maintained. Every entry is&lt;br&gt;
green. Meanwhile a chunk of them lead to a Telegram page saying nothing, and the only&lt;br&gt;
person who finds out is the reader who clicks — once, and then never trusts the list&lt;br&gt;
again.&lt;/p&gt;

&lt;p&gt;I hit this while building a catalog of Telegram Mini Apps. Every entry passed a&lt;br&gt;
conventional link check. Only after adding the title check could I say the bots were&lt;br&gt;
actually there. It now runs weekly in CI and flags anything that has quietly&lt;br&gt;
disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats worth stating
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This depends on Telegram's markup, which is not an API contract. If they change the
placeholder strings, the check goes stale. Pin the strings somewhere obvious and
test against a known-dead username occasionally.&lt;/li&gt;
&lt;li&gt;A bot that exists but is broken, banned, or unresponsive still passes. This tells
you the username resolves — nothing more.&lt;/li&gt;
&lt;li&gt;Fetch politely. This is one request per entry, on a schedule, not a crawl.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The general shape
&lt;/h2&gt;

&lt;p&gt;The reusable lesson is not about Telegram. It is that &lt;strong&gt;HTTP 200 means the server&lt;br&gt;
answered, not that the thing exists.&lt;/strong&gt; Plenty of platforms serve a decorated&lt;br&gt;
"nothing here" page rather than a 404 — it is better for humans and worse for every&lt;br&gt;
automated check pointed at them.&lt;/p&gt;

&lt;p&gt;Whenever you are checking a resource on someone else's platform, the question to ask&lt;br&gt;
is: what does this host return for something that definitely is not there? Find out&lt;br&gt;
first, then write the check. Assuming a 404 you never verified is how a monitor ends&lt;br&gt;
up reporting green for months.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The catalog this came out of is&lt;br&gt;
&lt;a href="https://github.com/coolpac/telegram-mini-apps-catalog" rel="noopener noreferrer"&gt;here&lt;/a&gt; — the check lives in&lt;br&gt;
&lt;code&gt;scripts/check.py&lt;/code&gt;, public domain, take it. I also build&lt;br&gt;
&lt;a href="https://storiesfly.com" rel="noopener noreferrer"&gt;StoriesFly&lt;/a&gt;, which is where the Instagram-flavoured test&lt;br&gt;
usernames above came from.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>python</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
