How Telegram Desktop Implements Chat Invite Links and Chatlist Invites
Telegram Desktop processes chat invite links and chatlist invites through parallel subsystems that share an MTProto API layer but diverge in UI handling, using messages.checkChatInvite for individual invites and chatlists.exportChatlistInvite for filtered chat lists.
The telegramdesktop/tdesktop codebase handles both individual chat invitations and filtered chat list sharing through a sophisticated multi-layered architecture. This implementation separates API communication, data persistence, and user interface concerns while maintaining strict adherence to the MTProto protocol. Understanding these internals reveals how the client manages permission checks, preview generation, and batch chat subscriptions.
Core API Architecture
Both subsystems funnel requests through ApiWrap::request() in apiwrap.cpp (lines 366-370), which provides generic error handling and authentication layers. The implementation maps MTProto TL methods to C++ wrappers that bridge the protocol buffer layer with the Qt-based UI framework.
The primary API distinctions include:
- Individual invites: Use
messages.checkChatInviteandmessages.importChatInvitemethods - Chatlist invites: Use
chatlists.exportChatlistInvite,chatlists.editExportedInvite, andchatlists.joinChatlistInvitemethods
Individual Chat Invite Links Implementation
Checking and Validating Invites
When a user triggers an invite link—either by clicking an external URL or pasting a hash—the client calls CheckChatInvite() in api/api_chat_invite.cpp (lines 61-73). This function sends the messages.checkChatInvite query to the server and handles three possible response constructors:
messages.chatInvite- Standard invitation requiring user confirmationmessages.chatInviteAlready- User is already a membermessages.chatInvitePeek- Preview of a private invite
If the invite requires a subscription payment, the UI instantiates ConfirmSubscriptionBox (lines 19-46); otherwise, it creates ConfirmInviteBox (lines 104-112).
UI Presentation and Join Flow
The ConfirmInviteBox class in boxes/peers/edit_peer_invite_links.cpp (constructor at line 72) renders the invitation preview, displaying the chat title, avatar, participant count, and expiry information. When the user presses the join button, SubmitChatInvite() (lines 51-66 in api/api_chat_invite.cpp) dispatches messages.importChatInvite to the server.
// api/api_chat_invite.cpp:61-73
void CheckChatInvite(
not_null<Window::SessionController*> controller,
const QString &hash,
ChannelData *invitePeekChannel,
Fn<void()> loaded) {
const auto session = &controller->session();
const auto weak = base::make_weak(controller);
session->api().checkChatInvite(hash,
[=](const MTPChatInvite &result) {
// Handle constructors and show ConfirmInviteBox
},
[=](const MTP::Error &error) { /* Error handling */ });
}
On successful joining, the client opens the chat history via Window::SessionController::showPeerHistory.
Chatlist Invite Links Implementation
Chatlist invites (also called folder invites) allow users to share entire filtered sets of chats through a single link. Unlike individual invites, these operate on the Data::ChatFilterLink data structure defined in data/data_chat_filters.h.
Exporting Chatlist Invites
When a user clicks Export link in the filter editor, ExportFilterLink() in boxes/filters/edit_filter_links.cpp (lines 1080-1087) constructs an MTP_vector<MTPInputPeer> from the filter's chat list and calls chatlists.exportChatlistInvite.
// boxes/filters/edit_filter_links.cpp:1080-1087
void ExportFilterLink(
FilterId id,
const std::vector<not_null<PeerData*>> &peers,
Fn<void(Data::ChatFilterLink)> done,
Fn<void(QString)> fail) {
const auto session = &peers.front()->session();
auto mtpPeers = peers |
ranges::views::transform([](not_null<PeerData*> p){
return MTPInputPeer(p->input());
})
| ranges::to<QVector<MTPInputPeer>>();
session->api().request(MTPchatlists_ExportChatlistInvite(
MTP_inputChatlistDialogFilter(MTP_int(id)),
MTP_string(), // Empty title for default
MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
.done([=](const MTPchatlists_ExportedChatlistInvite &result) {
// Store via Data::ChatFilters::add()
})
.fail([=](const MTP::Error &error) { fail(error.type()); })
.send();
}
The server returns a ChatlistExportedInvite containing a unique slug, which the client persists through Data::ChatFilters::add() in data/data_chat_filters.cpp (lines 543-553).
Editing and Managing Links
The EditLinkChats() function (lines 1040-1064 in boxes/filters/edit_filter_links.cpp) handles modifications to existing chatlist invites. It sends chatlists.editExportedInvite with updated peer vectors while preserving the original URL slug.
// boxes/filters/edit_filter_links.cpp:1040-1064
void EditLinkChats(
const Data::ChatFilterLink &link,
base::flat_set<not_null<PeerData*>> peers,
Fn<void(QString)> done) {
const auto session = &peers.front()->session();
auto mtpPeers = peers |
ranges::views::transform([](not_null<PeerData*> p){
return MTPInputPeer(p->input());
})
| ranges::to<QVector<MTPInputPeer>>();
session->api().request(MTPchatlists_EditExportedInvite(
MTP_flags(MTPchatlists_EditExportedInvite::Flag::f_peers),
MTP_inputChatlistDialogFilter(MTP_int(link.id)),
MTP_string(link.url),
MTPstring(), // Title unchanged
MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
.done([=](const MTPExportedChatlistInvite &) { done(QString()); })
.fail([=](const MTP::Error &error) { done(error.type()); })
.send();
}
Error Handling and UI Feedback
Both subsystems rely on ApiWrap::request() for error propagation. Specific error strings like INVITE_REQUEST_SENT, USERS_TOO_MUCH, and generic "bad link" responses are mapped to localized toast notifications in CheckChatInvite (lines 93-115) and through ShowSaveError / ShowEmptyLinkError for chatlist operations (lines 108-115).
The UI components for displaying links include:
InviteLinkLabelandInviteLinkButtonsin ui/controls/invite_link_label.h/.cpp and ui/controls/invite_link_buttons.h/.cppLinkRowin boxes/filters/edit_filter_links.cpp (lines 35-53) for chatlist link listings
Key Files and Architecture
The implementation maintains strict separation across three layers:
- API Layer: api/api_chat_invite.cpp, apiwrap.cpp, and mtproto/scheme/api.tl handle protocol communication
- Data Layer: data/data_chat_filters.cpp persists chatlist invite state
- UI Layer: boxes/peers/edit_peer_invite_links.cpp and boxes/filters/edit_filter_links.cpp manage user interactions
Summary
- Telegram Desktop implements chat invite links and chatlist invites as parallel subsystems with distinct MTProto methods but shared error handling infrastructure
- Individual invites use
messages.checkChatInviteandmessages.importChatInvite, processed throughCheckChatInvite()andSubmitChatInvite()in api/api_chat_invite.cpp - Chatlist invites rely on
chatlists.exportChatlistInviteandchatlists.editExportedInvite, implemented in boxes/filters/edit_filter_links.cpp - The
Data::ChatFilterLinkstructure in data/data_chat_filters.h persists chatlist invite metadata including slugs and peer lists - All API requests flow through
ApiWrap::request()in apiwrap.cpp, which attaches standardized error handling and authentication
Frequently Asked Questions
What is the difference between chat invite links and chatlist invites in Telegram Desktop?
Chat invite links provide access to individual groups or channels through messages.checkChatInvite, while chatlist invites allow sharing entire filtered chat collections via chatlists.exportChatlistInvite. The former uses the ConfirmInviteBox UI component, whereas the latter utilizes Data::ChatFilterLink structures and the LinkRow class for management.
How does the client handle expired or invalid invite links?
Invalid links trigger error responses through ApiWrap::request() that are caught in CheckChatInvite (lines 93-115) and translated to localized toast notifications. Specific error types like INVITE_REQUEST_SENT or USERS_TOO_MUCH generate distinct user-facing messages without crashing the application.
Which data structure stores chatlist invite information?
The Data::ChatFilterLink structure defined in data/data_chat_filters.h stores chatlist invite metadata including the URL slug, title, and associated chat list. The Data::ChatFilters manager class maintains the mapping between filter IDs and these link objects in data/data_chat_filters.cpp.
Where does the actual API request construction happen for joining a chat?
The SubmitChatInvite() function in api/api_chat_invite.cpp (lines 51-66) constructs the MTPmessages_ImportChatInvite request using the invite hash, while chatlist modifications use EditLinkChats() in boxes/filters/edit_filter_links.cpp to build MTPchatlists_EditExportedInvite requests with updated peer vectors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →