Pick a Telegram Bot Username
You help the user find a short, memorable, available Telegram bot username that satisfies BotFather's rules.
Arguments
The user provides free-form context, typically a topic and audience. Examples:
/gramio-pick-username weather bot for Russian users
/gramio-pick-username крипто-трекер, русскоязычная аудитория
/gramio-pick-username is @weatherly_bot free?
If the user only gives a topic, proceed without asking — generate candidates, check availability, report back. Ask only when the topic is genuinely ambiguous (e.g. single word with multiple meanings) or the target audience is impossible to guess.
Rules BotFather enforces
Validate every candidate against all of these before wasting a fetch on it:
- Length: 5–32 characters total.
- Charset:
a-z, A-Z, 0-9, _ only (case-insensitive at lookup time, but store lowercase).
- Suffix: must end in
bot (case-insensitive — FooBot, foo_bot, fooBOT all legal). Exception: collectible usernames on Fragment skip the bot suffix but require a paid upgrade (1000+ TON) — do not suggest these unless the user explicitly asks for short/premium names.
- Underscores: cannot start or end with
_, cannot contain consecutive __.
- First character: must be a letter (cannot start with a digit or underscore).
- Reserved / profane: Telegram silently rejects some words; if a legal-looking name is refused in BotFather, drop it and move on.
Any candidate violating 1–5 is filtered out locally before any network check.
Candidate-generation strategy
Given topic T and audience language L:
- Obvious direct forms —
{T}bot, {T}_bot, the{T}bot, my{T}bot, {T}ai_bot, {T}hub_bot. Almost always taken for popular topics, but you must rule them out explicitly.
- Prefixes —
my, the, get, ask, hey, now, ok, tap, go, it, ai, hub, box, pal, peek, lens.
- Suffix variants before
bot — ly, ify, io, hq, lab, kit, zen, wise, mate, pal.
- Transliteration for non-English audiences — e.g. for Russian weather bot:
pogoda, kakpogoda, chopogoda, pogoday, pogodnik, pogodushka, gradus, nebo. Always include both English and transliterated forms when L is not English.
- Domain synonyms — pull from the topic's jargon. Weather →
meteo, forecast, sky, cloud, rain, climate, nimbus, breeze. Crypto → chain, block, token, ledger, hodl. Tasks → todo, task, doit, tick, check.
- Soft mutations — drop a vowel (
weathr), add r/y/q (climatiq, weathery), swap s→z. Keep readable.
- Two-word merges that read as one —
weatherit_bot ✓ (reads cleanly) ; weather_one_bot ✗ (two words plus bot = visually noisy). Prefer the merge that lets bot fuse into the tail.
Generate ~20 candidates before any network call. More = wasted fetches; fewer = likely all taken.
Availability check (t.me button inspection)
The truth signal is the main CTA button on https://t.me/<username>:
| Button text |
og:title pattern |
Verdict |
Start Bot |
Telegram: Launch @xxx |
taken — live bot |
View Bot |
Telegram: Contact @xxx |
taken — bot without /start handler, still reserved |
Send Message |
generic "Telegram: Contact" |
free — username not registered as bot or user |
View Channel / View Group |
— |
taken by non-bot (rare for *bot suffix, but possible) |
Additional taken-signals to double-check: presence of avatar image, tgme_page_description block, Subscribe button.
Critical caveats (spell these out to the user in the final report)
- t.me always returns HTTP 200 and shows the boilerplate "If you have Telegram, you can contact @..." page. The boilerplate text says nothing about availability — only the button text does.
- "Free on t.me" ≠ "creatable in BotFather". If a bot owner deletes their bot via
/deletebot, the username often stays reserved and BotFather refuses /newbot on it. The only ground truth is attempting /newbot in @BotFather. Always tell the user to do this final check before committing.
- Case doesn't matter.
@WeatherBot and @weatherbot resolve to the same account. Lowercase your candidates before fetching.
- Rate limits exist. If you batch too aggressively and start seeing 429s or rate-limit pages, back off and slow the batch.
Availability check — use the bundled script, not WebFetch
This skill ships with check-usernames.mjs in its own directory. Always use it for availability checks — do not fall back to WebFetch per-URL.
Why:
- One subprocess invocation returns a compact JSON verdict per username instead of 20+ KB of raw HTML per fetch. Your context stays clean.
- It validates each name locally (length, charset, suffix, leading digit, consecutive/trailing underscores) before fetching, so invalid candidates cost zero requests.
- Parallelism is handled in-process with a bounded worker pool (default 8) — no wall-of-tool-calls.
- Runs on any machine with Node ≥18, Bun, or Deno (all ship a native
fetch). Zero dependencies. No bash / no Windows sh quirks — portable across Claude Code, Cursor, Copilot, and any other agent runtime that can spawn node.
How to invoke
# Pass candidates as CLI args (preferred for small batches)
node skills/gramio-pick-username/check-usernames.mjs --json \
weatherbot weatheritbot meteobot kakpogodabot nebobot
# Or pipe from stdin (useful for larger lists)
printf 'weatherbot\nweatheritbot\nmeteobot\n' | \
node skills/gramio-pick-username/check-usernames.mjs --json
# Tuning knobs
node … --concurrency 4 --timeout 10000 foo_bot bar_bot
From inside the user's project, the script lives under whatever path they installed the skills to — usually ./skills/gramio-pick-username/check-usernames.mjs. Check the cwd before running, and if the script isn't where you expect, fall back to bun or copy the script to a known location.
JSON output schema
The --json flag emits an array of result objects:
[
{
"username": "weatheritbot",
"verdict": "free",
"kind": "unclaimed",
"button": "Send Message",
"ogTitle": "Telegram: Contact @weatheritbot",
"hasAvatar": false,
"status": 200
},
{
"username": "weatherbot",
"verdict": "taken",
"kind": "bot_live",
"button": "Start Bot",
"hasAvatar": true,
"status": 200
},
{
"username": "1foo",
"verdict": "invalid",
"reasons": ["MUST_START_WITH_LETTER", "MISSING_BOT_SUFFIX"]
}
]
Verdicts: free · taken · invalid · rate_limited · error · unknown.
Kinds (when taken): bot_live · bot_no_start · user · channel · group.
Handling non-clean results
rate_limited → back off: reduce --concurrency to 2–3, wait ~60s, retry only those names. Do not spam the endpoint.
error → retry once with a higher --timeout. If it still fails, mark the name as "unknown, verify manually" in the final report — do not silently treat it as free.
unknown → the HTML didn't match known patterns (rare). Fetch the page manually once via WebFetch to inspect, then update the classifier if you've found a new pattern.
Final ranking (criteria for the shortlist)
Rank surviving free candidates by:
- Length — prefer ≤16 chars (comfortable inline mention, fits in bio/ads).
- Readability in target language — a native reader should parse it at first glance, no syllable backtracking.
- No digits / no underscores if a clean alternative exists.
weatherit_bot beats weather_1_bot.
bot fuses into the word — weatheritbot > weatheronebot. Two-word roots + bot look like three tokens.
- Pronounceable aloud — useful for podcasts, demos, referrals.
- No trademark risk — flag candidates that collide with a known brand in the topic (e.g.
chatgpt_bot is a legal minefield).
Output format
Deliver a compact report the user can act on:
Topic: <topic> · Audience: <language/region>
✅ FREE (ranked)
1. @weatheritbot — 12 chars · reads clean in EN · "it" nod to imperative mood
2. @kakpogodabot — 11 chars · RU-native · "how's the weather" question form
3. @nebobot — 6 chars · RU · "sky" · very short, memorable
⚠️ FREE ON T.ME BUT VERIFY IN BOTFATHER
4. @breezybot — maybe deleted-and-reserved · try `/newbot` first
❌ TAKEN (checked)
- @weatherbot (Start Bot)
- @pogodabot (View Bot)
- @meteobot (Start Bot)
- ... (condensed list, grouped)
NEXT STEP
Open @BotFather → /newbot → paste "@weatheritbot" when asked for the username.
If BotFather rejects it, try the next one down the list.
Anti-patterns to avoid
- Don't judge availability from the "If you have Telegram" boilerplate text — it's identical for free and taken names.
- Don't rely on HTTP status codes — they're always 200.
- Don't suggest names with consecutive
__, trailing _, or leading digit — BotFather rejects them and you'll have wasted the user's time.
- Don't skip the "verify in BotFather" reminder —
t.me free ≠ creatable.
- Don't propose
gpt/openai/telegram-prefixed names without flagging the trademark risk.
- Don't burn fetches on candidates that fail local validation. Validate, then fetch.
- Don't over-ask. If the topic is clear, just generate and check — deliver a shortlist, not a questionnaire.
After the user picks a name
Once the user picks a finalist from the shortlist and confirms BotFather accepted it, remind them to copy the bot's @username and the token BotFather returned into .env as BOT_TOKEN=... before starting the bot.
1---2name: gramio-pick-username3description: Pick an available Telegram bot username. Takes a topic (and optional audience/language), generates candidates respecting BotFather's rules, batch-checks availability on t.me via the bundled `check-usernames.mjs` script, and returns a ranked shortlist of free names. Use whenever the user asks "find a bot username", "check if @foo_bot is taken", "придумай юзернейм для бота", "неминг бота".4---56# Pick a Telegram Bot Username78You help the user find a short, memorable, **available** Telegram bot username that satisfies BotFather's rules.910## Arguments1112The user provides free-form context, typically a topic and audience. Examples:1314- `/gramio-pick-username weather bot for Russian users`15- `/gramio-pick-username крипто-трекер, русскоязычная аудитория`16- `/gramio-pick-username is @weatherly_bot free?`1718If the user only gives a topic, proceed without asking — generate candidates, check availability, report back. Ask only when the topic is genuinely ambiguous (e.g. single word with multiple meanings) or the target audience is impossible to guess.1920## Rules BotFather enforces2122Validate every candidate against **all** of these before wasting a fetch on it:23241. **Length**: 5–32 characters total.252. **Charset**: `a-z`, `A-Z`, `0-9`, `_` only (case-insensitive at lookup time, but store lowercase).263. **Suffix**: must end in `bot` (case-insensitive — `FooBot`, `foo_bot`, `fooBOT` all legal). *Exception*: collectible usernames on [Fragment](https://fragment.com) skip the `bot` suffix but require a paid upgrade (1000+ TON) — do not suggest these unless the user explicitly asks for short/premium names.274. **Underscores**: cannot start or end with `_`, cannot contain consecutive `__`.285. **First character**: must be a letter (cannot start with a digit or underscore).296. **Reserved / profane**: Telegram silently rejects some words; if a legal-looking name is refused in BotFather, drop it and move on.3031Any candidate violating 1–5 is filtered out locally before any network check.3233## Candidate-generation strategy3435Given topic `T` and audience language `L`:36371. **Obvious direct forms** — `{T}bot`, `{T}_bot`, `the{T}bot`, `my{T}bot`, `{T}ai_bot`, `{T}hub_bot`. Almost always taken for popular topics, but you must rule them out explicitly.382. **Prefixes** — `my`, `the`, `get`, `ask`, `hey`, `now`, `ok`, `tap`, `go`, `it`, `ai`, `hub`, `box`, `pal`, `peek`, `lens`.393. **Suffix variants before `bot`** — `ly`, `ify`, `io`, `hq`, `lab`, `kit`, `zen`, `wise`, `mate`, `pal`.404. **Transliteration for non-English audiences** — e.g. for Russian weather bot: `pogoda`, `kakpogoda`, `chopogoda`, `pogoday`, `pogodnik`, `pogodushka`, `gradus`, `nebo`. Always include both English and transliterated forms when `L` is not English.415. **Domain synonyms** — pull from the topic's jargon. Weather → `meteo`, `forecast`, `sky`, `cloud`, `rain`, `climate`, `nimbus`, `breeze`. Crypto → `chain`, `block`, `token`, `ledger`, `hodl`. Tasks → `todo`, `task`, `doit`, `tick`, `check`.426. **Soft mutations** — drop a vowel (`weathr`), add `r`/`y`/`q` (`climatiq`, `weathery`), swap `s`→`z`. Keep readable.437. **Two-word merges that read as one** — `weatherit_bot` ✓ (reads cleanly) ; `weather_one_bot` ✗ (two words plus `bot` = visually noisy). Prefer the merge that lets `bot` fuse into the tail.4445Generate **~20 candidates** before any network call. More = wasted fetches; fewer = likely all taken.4647## Availability check (t.me button inspection)4849The truth signal is the main CTA button on `https://t.me/<username>`:5051| Button text | og:title pattern | Verdict |52| --------------------- | -------------------------- | -------------------------------------------------------------------- |53| `Start Bot` | `Telegram: Launch @xxx` | **taken** — live bot |54| `View Bot` | `Telegram: Contact @xxx` | **taken** — bot without /start handler, still reserved |55| `Send Message` | generic "Telegram: Contact" | **free** — username not registered as bot *or* user |56| `View Channel` / `View Group` | — | **taken by non-bot** (rare for `*bot` suffix, but possible) |5758Additional taken-signals to double-check: presence of avatar image, `tgme_page_description` block, `Subscribe` button.5960### Critical caveats (spell these out to the user in the final report)6162- **t.me always returns HTTP 200** and shows the boilerplate "If you have Telegram, you can contact @..." page. The boilerplate text says **nothing** about availability — only the button text does.63- **"Free on t.me" ≠ "creatable in BotFather"**. If a bot owner deletes their bot via `/deletebot`, the username often stays **reserved** and BotFather refuses `/newbot` on it. The only ground truth is attempting `/newbot` in [@BotFather](https://t.me/BotFather). Always tell the user to do this final check before committing.64- **Case doesn't matter**. `@WeatherBot` and `@weatherbot` resolve to the same account. Lowercase your candidates before fetching.65- **Rate limits exist**. If you batch too aggressively and start seeing 429s or rate-limit pages, back off and slow the batch.6667## Availability check — use the bundled script, not WebFetch6869This skill ships with `check-usernames.mjs` in its own directory. **Always use it** for availability checks — do not fall back to WebFetch per-URL.7071Why:7273- One subprocess invocation returns a compact JSON verdict per username instead of 20+ KB of raw HTML per fetch. Your context stays clean.74- It validates each name locally (length, charset, suffix, leading digit, consecutive/trailing underscores) before fetching, so invalid candidates cost zero requests.75- Parallelism is handled in-process with a bounded worker pool (default 8) — no wall-of-tool-calls.76- Runs on any machine with Node ≥18, Bun, or Deno (all ship a native `fetch`). Zero dependencies. No bash / no Windows `sh` quirks — portable across Claude Code, Cursor, Copilot, and any other agent runtime that can spawn `node`.7778### How to invoke7980```bash81# Pass candidates as CLI args (preferred for small batches)82node skills/gramio-pick-username/check-usernames.mjs --json \83 weatherbot weatheritbot meteobot kakpogodabot nebobot8485# Or pipe from stdin (useful for larger lists)86printf 'weatherbot\nweatheritbot\nmeteobot\n' | \87 node skills/gramio-pick-username/check-usernames.mjs --json8889# Tuning knobs90node … --concurrency 4 --timeout 10000 foo_bot bar_bot91```9293From inside the user's project, the script lives under whatever path they installed the skills to — usually `./skills/gramio-pick-username/check-usernames.mjs`. Check the cwd before running, and if the script isn't where you expect, fall back to `bun` or copy the script to a known location.9495### JSON output schema9697The `--json` flag emits an array of result objects:9899```json100[101 {102 "username": "weatheritbot",103 "verdict": "free",104 "kind": "unclaimed",105 "button": "Send Message",106 "ogTitle": "Telegram: Contact @weatheritbot",107 "hasAvatar": false,108 "status": 200109 },110 {111 "username": "weatherbot",112 "verdict": "taken",113 "kind": "bot_live",114 "button": "Start Bot",115 "hasAvatar": true,116 "status": 200117 },118 {119 "username": "1foo",120 "verdict": "invalid",121 "reasons": ["MUST_START_WITH_LETTER", "MISSING_BOT_SUFFIX"]122 }123]124```125126Verdicts: `free` · `taken` · `invalid` · `rate_limited` · `error` · `unknown`.127Kinds (when `taken`): `bot_live` · `bot_no_start` · `user` · `channel` · `group`.128129### Handling non-clean results130131- `rate_limited` → back off: reduce `--concurrency` to 2–3, wait ~60s, retry only those names. Do not spam the endpoint.132- `error` → retry once with a higher `--timeout`. If it still fails, mark the name as "unknown, verify manually" in the final report — do not silently treat it as free.133- `unknown` → the HTML didn't match known patterns (rare). Fetch the page manually once via WebFetch to inspect, then update the classifier if you've found a new pattern.134135## Final ranking (criteria for the shortlist)136137Rank surviving free candidates by:1381391. **Length** — prefer ≤16 chars (comfortable inline mention, fits in bio/ads).1402. **Readability in target language** — a native reader should parse it at first glance, no syllable backtracking.1413. **No digits / no underscores** if a clean alternative exists. `weatherit_bot` beats `weather_1_bot`.1424. **`bot` fuses into the word** — `weatheritbot` > `weatheronebot`. Two-word roots + `bot` look like three tokens.1435. **Pronounceable aloud** — useful for podcasts, demos, referrals.1446. **No trademark risk** — flag candidates that collide with a known brand in the topic (e.g. `chatgpt_bot` is a legal minefield).145146## Output format147148Deliver a compact report the user can act on:149150```151Topic: <topic> · Audience: <language/region>152153✅ FREE (ranked)1541. @weatheritbot — 12 chars · reads clean in EN · "it" nod to imperative mood1552. @kakpogodabot — 11 chars · RU-native · "how's the weather" question form1563. @nebobot — 6 chars · RU · "sky" · very short, memorable157158⚠️ FREE ON T.ME BUT VERIFY IN BOTFATHER1594. @breezybot — maybe deleted-and-reserved · try `/newbot` first160161❌ TAKEN (checked)162- @weatherbot (Start Bot)163- @pogodabot (View Bot)164- @meteobot (Start Bot)165- ... (condensed list, grouped)166167NEXT STEP168Open @BotFather → /newbot → paste "@weatheritbot" when asked for the username.169If BotFather rejects it, try the next one down the list.170```171172## Anti-patterns to avoid173174- **Don't** judge availability from the "If you have Telegram" boilerplate text — it's identical for free and taken names.175- **Don't** rely on HTTP status codes — they're always 200.176- **Don't** suggest names with consecutive `__`, trailing `_`, or leading digit — BotFather rejects them and you'll have wasted the user's time.177- **Don't** skip the "verify in BotFather" reminder — `t.me` free ≠ creatable.178- **Don't** propose `gpt`/`openai`/`telegram`-prefixed names without flagging the trademark risk.179- **Don't** burn fetches on candidates that fail local validation. Validate, *then* fetch.180- **Don't** over-ask. If the topic is clear, just generate and check — deliver a shortlist, not a questionnaire.181182## After the user picks a name183184Once the user picks a finalist from the shortlist and confirms BotFather accepted it, remind them to copy the bot's `@username` and the token BotFather returned into `.env` as `BOT_TOKEN=...` before starting the bot.