gramio-pick-username

Installation
SKILL.md

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:

  1. Length: 5–32 characters total.
  2. Charset: a-z, A-Z, 0-9, _ only (case-insensitive at lookup time, but store lowercase).
  3. 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.
  4. Underscores: cannot start or end with _, cannot contain consecutive __.
  5. First character: must be a letter (cannot start with a digit or underscore).
  6. 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:

  1. 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.
  2. Prefixesmy, the, get, ask, hey, now, ok, tap, go, it, ai, hub, box, pal, peek, lens.
  3. Suffix variants before botly, ify, io, hq, lab, kit, zen, wise, mate, pal.
  4. 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.
  5. 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.
  6. Soft mutations — drop a vowel (weathr), add r/y/q (climatiq, weathery), swap sz. Keep readable.
  7. Two-word merges that read as oneweatherit_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:

  1. Length — prefer ≤16 chars (comfortable inline mention, fits in bio/ads).
  2. Readability in target language — a native reader should parse it at first glance, no syllable backtracking.
  3. No digits / no underscores if a clean alternative exists. weatherit_bot beats weather_1_bot.
  4. bot fuses into the wordweatheritbot > weatheronebot. Two-word roots + bot look like three tokens.
  5. Pronounceable aloud — useful for podcasts, demos, referrals.
  6. 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.

Related skills

More from gramiojs/documentation

Installs
1
GitHub Stars
7
First Seen
Apr 20, 2026