Appearance
Games
Not implemented
No part of this works yet — the gateway route is live, but no bot token can be issued, so no method can be called. See implementation status.
A game is an HTML5 page you host, launched from a card the bot posts in a chat. The platform handles the card, the Play button, the leaderboard rendering and the score storage. You handle the game itself and the server that decides what a score is.
Games are specified in the project TZ as B17, at priority P2 — behind the core messaging surface. The method shapes follow the Telegram Bot API, so Telegram semantics apply wherever the specification is silent.
Games are identified by short name, not URL
This is the first thing that surprises people arriving with a URL in hand.
A game is registered once with the bot — through @gcore, the platform's bot-management bot — with a short_name, a title, a description, an image, an optional GIF, and the URL the game is hosted at. From then on you send the short name:
json
{ "chat_id": 152874431, "game_short_name": "tower_blocks" }The consequences are worth spelling out:
- Moving the game to a different host does not change your sending code. You update the registration; every card already in every chat continues to work.
- A short name belongs to one bot. Your bot cannot send a game registered to somebody else's bot — the call is rejected, not rendered as text.
- An unregistered short name is an error, not a typo you find in production.
sendGamefails rather than posting a broken card.
You cannot register a game today
@gcore is not implemented and no bot token can be issued, so there is no way to register a short name. This section describes the model, not a procedure you can follow. Status.
Sending the card
sendGame posts the card. With no reply_markup it carries a single Play button, which is what you want almost always.
python
await bot.send_game(chat_id=chat_id, game_short_name="tower_blocks")javascript
await bot.api.sendGame(chatId, 'tower_blocks')bash
curl -X POST "https://api.casinoplaneta.info/bot$TOKEN/sendGame" \
-H 'Content-Type: application/json' \
-d '{"chat_id": 152874431, "game_short_name": "tower_blocks"}'
# Today: HTTP 404 — the gateway does not exist.If you do supply reply_markup, its first button must be a callback_game button. That is the button the platform turns into Play. Supply a keyboard whose first entry is an ordinary URL or callback button and the card has no way to launch anything — a silent-looking failure that is really a keyboard you built wrong.
Games can also be returned from inline mode as a game result, which is how a game gets into a chat the bot is not a member of.
How launching works
- The user taps Play. The client asks the platform for a launch answer for that button.
- The answer contains a URL, which the client opens in a webview.
- The URL fragment carries an identifier for the player.
That third point is the one to think hard about. The fragment is a hint about who is playing, delivered through a client, into a page you host. It tells your game which player to display, and it is not on its own proof of who submitted a score — anyone who can open a URL can put whatever they like in a fragment.
Scoring, and why it resists cheating
setGameScore records a score for a user against the specific game message.
python
await bot.set_game_score(
user_id=152874431,
score=4820,
chat_id=152874431,
message_id=1487,
)Two behaviours do most of the work here.
Scores only go up
By default the call is refused if the new score is not higher than the score already stored for that user in that message. This is not a convenience — it is the anti-replay rule. A game client that resubmits an old result, or an attacker replaying a captured submission, cannot walk the table backwards or overwrite a better score with a worse one.
force overrides it. The intended use is correcting a score your own backend awarded wrongly. Passing force on every submission because the refusals were annoying disables the protection entirely, and it is worth a comment in your code explaining why any use of it is there.
The bot's server is the authority
The call comes from your server, with your token. The game page in the webview cannot call setGameScore — it has no token, and it must not be given one. The pattern that holds up:
- The game page sends its result to your backend.
- Your backend decides whether that result is plausible.
- Your backend calls
setGameScore.
Skip the middle step and the high-score table is whatever the most motivated player types into a console. Nothing in the platform validates that a score was earned; it only enforces that scores increase.
Addressing the message
Give either chat_id + message_id, or inline_message_id for a game sent inline. Both, or neither, is an error. Store whichever pair applies when you send the card — you cannot reconstruct it later from the player alone, because the same player can have separate scores in separate messages.
Message edits
When a score lands, the platform rewrites the game message in place so the card shows the current standings. If several scores arrive in quick succession that becomes visible churn, and disable_edit_message turns it off — the score is still stored, the card just stops re-rendering.
Reading the leaderboard
getGameHighScores returns a window: the target user, a few neighbours on either side, and the top of the table when the user is far from it.
This is not a full leaderboard
There is no paging and no way to ask for all scores. A game that needs a complete ranking — for a season, a prize, a website — has to keep its own copy of every score it submits. Build that from the start if you will need it; you cannot backfill it from the platform later.
Scores are scoped to the game message, not globally to the game. The same player in two different chats has two independent scores, and that is usually what a group chat wants: the leaderboard on the card is the leaderboard for the people in that chat.
Errors
sendGame fails with the ordinary sending errors — unknown chat, not a member, blocked, insufficient rights, slow mode. setGameScore and getGameHighScores add message_not_found and not_your_message, which is what you get for a message another bot sent. See errors for the envelope and limits for pacing.
Note the failure mode that is not an error: a score refused for not being higher returns a failure and leaves the stored value untouched. That is the system working. Treat it as an expected branch rather than something to retry.