owpengram-server/docs/deep-links.md
Astra c39011e542 admin: allow shorter operator-assigned usernames, add reference docs
Lower the username floor to 3 characters on the admin-only write path
(account/channel set-username), separate from the 5-character self-service
minimum, since a deliberately short operator handle isn't the squatting the
higher floor guards against. Channel admin username assignment previously
had no length/format validation at all; this adds it.

Also adds three reference docs: the client's deep-link (tg://, owpg://)
surface, an OpenAPI 3.1 spec for both admin HTTP APIs (built from source,
not the stale docs/admin-panel-api.en.md), and a starter reserved-username
blocklist.
2026-09-17 15:24:31 +01:00

7.9 KiB

Client deep links

The desktop client (owpengram-desktop-client, a Telegram Desktop fork) resolves tg://<path> URIs and https://t.me/<path> links through the same handler table: Telegram/SourceFiles/core/local_url_handlers.cpp. Both schemes are normalized to the same <path>?<query> form before dispatch, so every entry below works as either tg://join?invite=... or t.me/join/... (the client also accepts the shorter t.me/+... / t.me/joinchat/... aliases, which get rewritten to join?invite= before reaching this table).

Patterns are matched in order top to bottom; the first regex match wins.

Path handlers

Path Handler Purpose
join?invite=<hash> JoinGroupByHash Join a chat/channel via invite link hash.
addlist?slug=<slug> JoinFilterBySlug Import a shareable chat folder.
addstickers?set=<name> / addemoji?set=<name> ShowStickerSet Open a sticker or custom-emoji set for install.
addtheme?slug=<slug> ShowTheme Open a shared theme for install.
addstyle?slug=<slug> ShowAiStyle Open a shared AI compose style.
setlanguage?lang=<code> SetLanguage Switch/install the app's interface language.
msg_url?<params> ShareUrl Open the "share to chat" composer for an external URL.
confirmphone?<params> ConfirmPhone Confirm a phone number transfer/login code sent by SMS.
socks?<params> ApplySocksProxy Add/apply a SOCKS5 proxy config.
proxy?<params> ApplyMtprotoProxy Add/apply an MTProto proxy config.
webproxy?<params> ApplyWebProxy Add/apply a web proxy config.
passport?<params> ShowPassport Telegram Passport authorization request.
bg?<params> ShowWallPaper Open a shared chat wallpaper for install.
resolve?<params> ResolveUsernameOrPhone Resolve @username/phone to a peer; see resolve parameters below. This is the most common deep link kind.
privatepost?<params> ResolvePrivatePost Open a specific message inside a private channel by internal channel id + message id.
test_chat_theme?<params> ResolveTestChatTheme Preview a chat theme without installing it.
invoice?<params> ResolveInvoice Open a payment invoice (Telegram Payments / Stars).
premium_offer[?<params>] ResolvePremiumOffer Open the Premium subscription offer screen.
premium_multigift?<params> ResolvePremiumMultigift Open the bulk/multi-recipient Premium gifting screen.
login?code=<n> ResolveLoginCode Apply a login confirmation code (QR/code login flow).
boost?<params> ResolveBoost Open the "boost this channel" flow.
message?slug=<slug> ResolveChatLink Open a pre-composed shareable chat/business link.
stars_topup?<params> ResolveTopUp Open the Telegram Stars top-up screen.
nft?slug=<slug> ResolveUniqueGift Open a collectible/unique gift by slug.
stargift_auction?slug=<slug> ResolveGiftAuction Open a Star Gift auction listing.
call?slug=<slug> ResolveConferenceCall Join a group/conference call by invite slug.
stars[?<params>] ResolveStarsSettings Open Telegram Stars balance/settings.
ton[?<params>] / grams[?<params>] ResolveTonSettings Open TON wallet settings.
oauth?<params> ResolveOAuth Third-party OAuth authorization via Telegram.
(anything else) HandleUnknown Fallback — shown as an unrecognized-link message.

Resolve parameters

resolve?domain=<username> (or the bare t.me/<username> shorthand, which the client rewrites to this form) accepts additional query parameters that change what opens once the peer is resolved:

Parameter Effect
domain The @username to resolve (required unless using the bare t.me/<username> form).
phone Resolve by phone number instead of username.
appname Open a Telegram Mini App by name instead of the peer's chat.
startapp Start-parameter passed into a Mini App launch.
attach Open the target's attach-menu bot alongside the chat.
start Start-parameter for a bot (/start <token>).
startgroup Add a bot to a group with this start token (bot-in-group flow).
startchannel Add a bot to a channel with this start token.
post Jump to a specific message id in the resolved peer.
story Open a specific story id from the resolved peer.
comment Jump to a specific comment/reply message id (discussion groups).
topic Open a specific forum topic id.
thread Open a specific reply thread/topic id (older alias of topic).
game Launch a specific inline game short name from a bot.
text Pre-fill the message composer with this text.
voicechat / videochat / livestream Join the peer's active voice chat / video chat / live stream by hash.

InternalUrlHandlers() in the same file (internal:<path>) covers links the app generates for its own in-app UI (e.g. internal:edit_peer/<id>, internal:common_groups/<id>, internal:collectible_username/<name>@<id>). These are not meant to be shared externally and are not part of the public deep-link surface — they only work as links the client itself produces inside its own UI.

This fork adds a second scheme, handled in Telegram/SourceFiles/core/application.cpp (Application::openLocalUrl / Application::openOwpengramUrl), for operating across multiple OwpenGram-compatible backends rather than only the official Telegram DCs.

Form Purpose
owpg://addserver?host=<host>&port=<port> Opens AddServerBox pre-filled with just the address. Deliberately carries nothing else — no name, description, RSA key, or DC id.
owpg://oauth?<params> / owpg://resolve?<params> Rewritten to the equivalent tg://oauth / tg://resolve command and dispatched on the current session — lets those two flows work on an OwpenGram account, not just official Telegram.
owpg://<host>/<rest> Routes a normal t.me-style path (<rest> = +<invite>, <username>, etc.) to whichever logged-in account is on that server's <host>, bypassing the official-Telegram-only guard. If no account on the device is logged into <host>, the client shows an inform box naming the server instead of silently failing.

Why addserver only takes host+port

The RSA public key, main DC id, display name, and description are never accepted from the link itself — see the comment above kOwpgAddServerRe in application.cpp. Letting a link author supply the key/identity directly would be a MITM vector: whoever crafts the link would control what the victim ends up trusting as that server's identity, regardless of who actually operates the host. Only host+port travel in the link; everything else is fetched live and is exactly as trustworthy as the TCP connection to that host:port already is.

Once AddServerBox has host+port, it calls Owpengram::FetchServerInfo(host, port, …), which does a plain HTTP GET against the server's own well-known endpoints on the same port:

Endpoint Returns
GET <host>:<port>/owpengram/server-info RSA public key (PEM), main DC id, and optional operator-set name/description/icon-presence flag.
GET <host>:<port>/owpengram/server-icon Raw icon bytes, only fetched when server-info reported hasIcon: true.

RefreshServersInfo() re-polls server-info/server-icon for every already-added custom server to keep a cosmetic name/icon change in sync — it never touches host, port, RSA key, DC id, or the server's local id, for the same MITM reason as above (see the doc comment on RefreshServersInfo in owpengram_servers.h).

This also means the RSA-key-in-a-URL scheme discussed earlier in this doc's history is intentionally not how this client adds a server — that was a plausible-looking design that this project already considered and rejected in favor of fetch-after-add.