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.
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. |
Internal-only links
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.
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=<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.