# Client deep links The desktop client (`owpengram-desktop-client`, a Telegram Desktop fork) resolves `tg://` URIs and `https://t.me/` links through the same handler table: `Telegram/SourceFiles/core/local_url_handlers.cpp`. Both schemes are normalized to the same `?` 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=` | `JoinGroupByHash` | Join a chat/channel via invite link hash. | | `addlist?slug=` | `JoinFilterBySlug` | Import a shareable chat folder. | | `addstickers?set=` / `addemoji?set=` | `ShowStickerSet` | Open a sticker or custom-emoji set for install. | | `addtheme?slug=` | `ShowTheme` | Open a shared theme for install. | | `addstyle?slug=` | `ShowAiStyle` | Open a shared AI compose style. | | `setlanguage?lang=` | `SetLanguage` | Switch/install the app's interface language. | | `msg_url?` | `ShareUrl` | Open the "share to chat" composer for an external URL. | | `confirmphone?` | `ConfirmPhone` | Confirm a phone number transfer/login code sent by SMS. | | `socks?` | `ApplySocksProxy` | Add/apply a SOCKS5 proxy config. | | `proxy?` | `ApplyMtprotoProxy` | Add/apply an MTProto proxy config. | | `webproxy?` | `ApplyWebProxy` | Add/apply a web proxy config. | | `passport?` | `ShowPassport` | Telegram Passport authorization request. | | `bg?` | `ShowWallPaper` | Open a shared chat wallpaper for install. | | `resolve?` | `ResolveUsernameOrPhone` | Resolve `@username`/phone to a peer; see [resolve parameters](#resolve-parameters) below. This is the most common deep link kind. | | `privatepost?` | `ResolvePrivatePost` | Open a specific message inside a private channel by internal channel id + message id. | | `test_chat_theme?` | `ResolveTestChatTheme` | Preview a chat theme without installing it. | | `invoice?` | `ResolveInvoice` | Open a payment invoice (Telegram Payments / Stars). | | `premium_offer[?]` | `ResolvePremiumOffer` | Open the Premium subscription offer screen. | | `premium_multigift?` | `ResolvePremiumMultigift` | Open the bulk/multi-recipient Premium gifting screen. | | `login?code=` | `ResolveLoginCode` | Apply a login confirmation code (QR/code login flow). | | `boost?` | `ResolveBoost` | Open the "boost this channel" flow. | | `message?slug=` | `ResolveChatLink` | Open a pre-composed shareable chat/business link. | | `stars_topup?` | `ResolveTopUp` | Open the Telegram Stars top-up screen. | | `nft?slug=` | `ResolveUniqueGift` | Open a collectible/unique gift by slug. | | `stargift_auction?slug=` | `ResolveGiftAuction` | Open a Star Gift auction listing. | | `call?slug=` | `ResolveConferenceCall` | Join a group/conference call by invite slug. | | `stars[?]` | `ResolveStarsSettings` | Open Telegram Stars balance/settings. | | `ton[?]` / `grams[?]` | `ResolveTonSettings` | Open TON wallet settings. | | `oauth?` | `ResolveOAuth` | Third-party OAuth authorization via Telegram. | | *(anything else)* | `HandleUnknown` | Fallback — shown as an unrecognized-link message. | ## Resolve parameters `resolve?domain=` (or the bare `t.me/` 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/` 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 `). | | `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. | ## Internal-only links `InternalUrlHandlers()` in the same file (`internal:`) covers links the app generates for its own in-app UI (e.g. `internal:edit_peer/`, `internal:common_groups/`, `internal:collectible_username/@`). 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. ## OwpenGram (`owpg://`) links 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=&port=` | Opens `AddServerBox` pre-filled with just the address. **Deliberately carries nothing else** — no name, description, RSA key, or DC id. | | `owpg://oauth?` / `owpg://resolve?` | 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:///` | Routes a normal `t.me`-style path (`` = `+`, ``, etc.) to whichever logged-in account is on that server's ``, bypassing the official-Telegram-only guard. If no account on the device is logged into ``, 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 :/owpengram/server-info` | RSA public key (PEM), main DC id, and optional operator-set name/description/icon-presence flag. | | `GET :/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.