Skip to content

bots: bots, inline mode, mini apps and payments on the registry - #13

Merged
erfnzdeh merged 10 commits into
mainfrom
feat/pr10-bots
Sep 4, 2026
Merged

bots: bots, inline mode, mini apps and payments on the registry#13
erfnzdeh merged 10 commits into
mainfrom
feat/pr10-bots

Conversation

@erfnzdeh

@erfnzdeh erfnzdeh commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

PR-10 of the v2 plan: bots, inline mode, mini apps and payments on the operation registry — 79 operations under bot, inline, webapp and payment, with tlgr/ops/_bots.py as shared plumbing.

  • Bots: start (private / group / channel, deep-link params), stop/restart, commands list and send, menu button, info, bot press — one dispatcher addressing buttons by --button <row,col|n|text> or --data, dispatching by kind (callback with/without password/game, url, login-url with explicit accept, switch-inline, request-peer, copy-text, webapp), url-auth that always shows domain/browser/IP before --yes, attach-menu bots, own-bot management, referral/affiliate programs, business-connected bot calls via --business-connection.
  • Inline: query with location/offset paging, list results, send a chosen result with the shared send options.
  • Mini apps: request main/simple/attach/menu web-view URLs (printed, never opened), prolong, send data, invoke custom method.
  • Payments: read-only — form, receipt, saved info, BIN info, subscriptions list/cancel; every fund movement or card/address entry is refused with exit 6 and asserted against the registry, not a command list.
  • Fixes a PR-1 bug: message get --json never populated reply_markup; each button now carries the flat index bot press takes.
  • Layer 229 only, refused with exit 13: ephemeral messages, welcome messages, rich buttons, chat-join web view.

Numbers

10 commits, 6,583 tests (225 new against a bot world in the fake Telegram), ruff + mypy clean. Parity: bots_inline_payments P0 13/13, 87.4% covered, 100% accounted; registry-wide P0 86 → 97, ids 678 → 831 (pre-rebase numbers).

Four modules of wire shapes for PR-10. Two carry the weight: Keyboard is the
write side of the reply-markup schema whose read side already lived in
models/message.py, so one JSON document round-trips between reading a keyboard
and pressing or re-sending a button in it; and PaymentForm carries
payable_here=false with the reason, because a caller deciding about a payment
needs the shape and needs to be told, in the payload, that tlgr will not pay.
PR-1 declared ReplyMarkup and nothing ever populated it, which made its two
P0 ids true only on paper: a caller could not see a button, so a caller could
not press one. message_to_model now fills it, each button carrying the flat
row-major n that 'bot press --button <n>' takes.

ops/_bots.py holds what all four new groups reach for — the button-type
vocabulary both sides share, the bot-session gate that turns Telegram's bare
BOT_METHOD_INVALID into exit 4 with a sentence, DC routing for inline message
ids, and the report option tree three commands walk.
The bot group as a user sees it (get, start, stop, command send, press, url-auth,
permission) and as its owner does (create, edit, token export, access, previews,
affiliate program, verification, menu, default rights).

bot press is the centre. Four button kinds hand a bot something the user owns —
a phone number, a location, a chat, a poll — and the protocol makes them look
like every other button; without its consent flag tlgr prints what it would
send and exits 2. A Pay button is refused outright with exit 6.

Five operations are registered and refuse with exit 13: bot ephemeral send and
delete, and the three bot welcome verbs, all of which need layer-229 methods the
pinned Telethon does not carry. Registering them is the point — 'unavailable in
this build' is a different answer from 'no such command'.
…ence

inline: a bot's own opaque next_offset is passed straight back rather than
wrapped in a signed cursor, 'inline send --pick' re-runs the query so the
(query_id, result_id) pair is never stale, and a bot that does not answer is an
empty page with a warning, not a failure.

webapp: 'webapp open' prints the signed URL and stops. There is no --open,
because that URL is a credential carrying the user's signed init data and a CLI
cannot host the mini-app bridge anyway.

payment: the whole checkout surface is readable and invoices can be created —
that asks somebody else to pay. sendPaymentForm, sendStarsForm,
validateRequestedInfo and fulfillStarsSubscription are absent from the surface,
and 'payment form get' says so in the payload rather than leaving it to an exit
code.
The fake grows bot state rather than canned replies: profile cards, per-scope
command lists, an attachment menu, a preview gallery, affiliate links,
payment shapes. 'bot access set --add' really appends to an allow-list a later
'bot access get' reads back, and 'bot preview edit --order' really reorders a
gallery.

222 tests. Three areas get more than a happy path: every consent-bearing
button has a test that presses without its flag and asserts nothing was sent;
every layer-229 operation has one asserting exit 13 and NOT_SUPPORTED, not 1;
and the payment policy is asserted against the registry — a future PR that
adds sendPaymentForm behind any flag fails there.

Two model defaults changed while writing them: an index whose default is 0 is
dropped by omit_defaults for exactly the first button, and a decision-carrying
boolean that reads as absent when false is a boolean a caller cannot act on.
…ms nothing

The bots_inline_payments domain waiver is gone. Its 22 remaining ids are named:
nine need API layer 229, twelve belong to another group's surface, one is the
story group's. P0 goes from 86 to 97 and total coverage from 678 to 831.

Registry lint L13 gains a second exemption. An operation that exists only to
refuse — the five layer-229 commands — must not claim catalog coverage for
something it cannot do, so it is tagged 'not-supported' instead of declaring a
partial cover that would inflate the number while doing nothing.

Two ids other PRs waived to this one are closed properly rather than
re-waived: 'bot stop' covers dialogs.bot-stop-restart, and 'bot ad list
--search' covers dialogs.sponsored-search-peers through
contacts.getSponsoredPeers — the ads surface, kept in one place.
contacts.getSponsoredPeers is the other place an ad appears; it comes back in
the same row shape so one 'bot ad read' marks either kind as seen.
…nd it

AGENT.md and README gain the group, both leading with what it will not do:
never pay, never open a browser, never press a disclosing button unasked.
CHANGELOG names the three absences and the reply_markup fix. DECISIONS records
the six calls that were not obvious — the not-supported tag, how a
confirmation an operation must see is spelled when --yes cannot reach it, the
'bot id' alias collision, why 'bot connection invoke' refuses, the one
reply-markup vocabulary, and reporting getEmojiGameInfo as it really answers.
ARCHITECTURE §9's rule, applied: a command that puts a message, a game, an
invoice, a typing action or a public badge where another person sees it says
so in its spec, and therefore in --help.
@erfnzdeh
erfnzdeh merged commit 87f491c into main Sep 4, 2026
20 checks passed
@erfnzdeh
erfnzdeh deleted the feat/pr10-bots branch September 4, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant