Skip to content

Commands

A command is a message that begins with /. The client offers an autocomplete menu when the user types the slash, built from a list the bot has declared in advance with setMyCommands.

That list is the only part the platform knows about. Nothing dispatches a command for you — an incoming /forecast Tashkent is an ordinary message whose text starts with a slash, and matching it is your handler's job.

Not implemented

The command-registration methods (setMyCommands / getMyCommands / deleteMyCommands) are verified live, but you cannot use them yet: no bot token can be issued (public token issuance is broken). This page describes the specified behaviour. Implementation status.

Declaring a list

bash
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/setMyCommands" \
  -H 'Content-Type: application/json' \
  -d '{
    "commands": [
      {"command": "start",    "description": "Start the bot"},
      {"command": "forecast", "description": "Five-day forecast for a city"},
      {"command": "settings", "description": "Change units and default city"}
    ],
    "scope": {"type": "all_private_chats"},
    "language_code": "en"
  }'

The command name is written without the leading slash, in 1–32 characters of lowercase Latin letters, digits and underscores. Uppercase is rejected rather than folded, so /Start in your declaration is an error, not a synonym. Descriptions run 1–256 characters. A single scope holds up to 100 commands.

Declaring is a one-off, not a per-boot chore. The list lives on the server until you overwrite it.

Scopes: narrower wins, and replaces

A bot does not have one command list. It has one per scope × language, and the client resolves which to show by walking from the narrowest match to the broadest:

ScopeApplies to
chat_memberOne specific user in one specific chat
chat_administratorsAdministrators of one specific chat
chatOne specific chat
all_chat_administratorsAdministrators of any group
all_group_chatsAll groups and supergroups
all_private_chatsAll private chats
defaultEverything not covered above

The first scope that holds a list wins — and it replaces the broader one instead of merging with it.

The usual reason commands vanish in groups

You set twenty commands on default, then later set two admin-only ones on all_chat_administrators. Administrators now see two commands, not twenty-two. Nothing merged; the narrow list shadowed the broad one entirely.

If a narrow scope should show extra commands on top of a broader list, you have to send the combined list yourself. There is no additive mode.

Languages: the empty language_code is the fallback

Inside each scope, resolution repeats over language: the client's own language first, then the entry stored with no language_code at all.

That untranslated entry is the fallback for every language you have not written. Which produces the second classic failure:

bash
# Sets the Russian list. And ONLY the Russian list.
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/setMyCommands" \
  -H 'Content-Type: application/json' \
  -d '{"commands": [{"command": "start", "description": "Запустить бота"}],
       "language_code": "ru"}'

A bot that only ever sets language_code: "ru" shows no commands whatsoever to everyone else — no menu, no autocomplete — because there is no fallback entry to fall back to. Write the untranslated list first, then layer translations over it:

python
API = f"https://api.casinoplaneta.info/bot{TOKEN}"

# 1. The fallback. Do this one first, always.
requests.post(f"{API}/setMyCommands", json={"commands": EN})

# 2. Translations, layered on top.
for lang, commands in {"ru": RU, "uz": UZ}.items():
    requests.post(f"{API}/setMyCommands",
                  json={"commands": commands, "language_code": lang})

Reading and clearing

getMyCommands reads one exact slot and does not walk the fallback chain. A scope you never wrote to returns [] — which is a normal answer, not a failure, and specifically not evidence that the user sees nothing there. To know what a given user actually sees, you have to resolve the chain yourself.

deleteMyCommands clears one exact slot too, and the next broader scope immediately takes over again. That makes it the tool for removing an override, not for leaving someone with an empty menu. To genuinely show nothing, write an empty array:

json
{"commands": [], "scope": {"type": "all_group_chats"}}

Two calls, opposite meanings

deleteMyCommands on all_group_chats → groups fall back to the default list. setMyCommands with [] on all_group_chats → groups show no commands at all.

Commands in groups

When several bots share a group, clients render commands as /cmd@botusername, and users may send either form. Your matcher has to accept both — strip an @suffix and compare it against your own username before dispatching, or a bot in a busy group will ignore half the commands aimed at it.

There is also a visibility question that has nothing to do with scopes. Under default privacy settings a bot in a group sees commands addressed to it, replies to its own messages, and service events — not general conversation. A command you never receive is usually a privacy-mode question, not a command-list one.

/start is the first thing almost every bot receives, because clients send it automatically when a user opens a chat with a bot for the first time. It is worth handling even if it only prints a greeting.

It can also carry a parameter. A link of the form:

https://code.casinoplaneta.info/my_bot?start=invite_9f21c0

is specified to open the bot and send /start invite_9f21c0 as the message text. The parameter is 1–64 characters of [A-Za-z0-9_-] — no spaces, no punctuation, no colons. It is a key, not a payload: put a token in the link and look the real data up server-side.

python
if msg["text"].startswith("/start"):
    parts = msg["text"].split(maxsplit=1)
    param = parts[1] if len(parts) > 1 else None
    if param:
        invite = db.lookup_invite(param)   # the link carried a key, not the data

Because the character set excludes almost everything, encode structured payloads before they go in the link — base64url fits the allowed set, arbitrary JSON does not.

A related form, ?startgroup=<param>, is specified to open the group picker and add the bot to a chosen group rather than starting a private chat. The full set of formats — Mini Apps, attachment menus, games — is on deep links.

Deep links do not open the app yet

The association file at code.casinoplaneta.info is published, but the Android app does not yet claim these links, so they open in a browser instead of the app. This is independent of the gateway being down — both halves are missing. See status.

The menu button

The button beside the composer in private chats is set with setChatMenuButton. It has three states: showing the command list (the default), opening a Mini App, or the system default. It can be set per chat or bot-wide, and it exists only in private chats — groups and channels have no menu button.

Propagation

Clients cache command lists rather than re-fetching them per keystroke, and refresh when the bot's info version changes. How quickly an update reaches an already-open client is not specified, and no propagation guarantee should be assumed from this document. Do not build a flow that depends on a command appearing within some number of seconds of setMyCommands returning.

See also