How to Implement a New Telegram API Method in ApiWrap: A Complete Workflow
Adding a new Telegram API method to ApiWrap requires extending the TL schema in api.tl, declaring the wrapper in apiwrap.h, implementing the request logic in apiwrap.cpp, and mapping the MTProto response to a domain object.
The ApiWrap class serves as the high-level façade between Telegram Desktop and the MTProto server, abstracting raw protocol details into Qt-friendly C++ interfaces. When implementing a new Telegram API method in ApiWrap, developers must follow a deterministic pipeline that touches the TL schema, auto-generated bindings, and the data model layer. This guide walks through the end-to-end workflow using concrete examples from the telegramdesktop/tdesktop codebase.
1. Extend the TL Schema Definition
Every API method begins in the Type Language (TL) schema. Open Telegram/SourceFiles/mtproto/scheme/api.tl and locate existing method definitions, such as messages_GetWallPaper around lines 1–10.
Add your new constructor following the exact MTProto specification:
messages_getFoo#12345678 flags:# peer:InputPeer limit:int = messages.Foo;
Run the CMake build to trigger the TL code generator. This automatically creates the MTPmessages_GetFoo C++ wrapper in the generated mtproto headers (e.g., mtproto_generated.h). No manual edits to generated files are required.
2. Declare the Wrapper Method in ApiWrap
With the MTProto bindings generated, expose the method through the high-level API. Open Telegram/SourceFiles/apiwrap.h and add a public member declaration following the pattern used by requestWallPaper (declared on lines 87–91).
void requestFoo(
const MTPInputPeer &peer,
int limit,
FnMut<void(const Data::FooResult&&)> done,
Fn<void(const MTP::Error&)> fail = nullptr);
Include any new headers needed for the result type. Add a private member variable to track the request ID near line 797 where _wallPaperRequestId is declared:
mtpRequestId _fooRequestId = 0;
3. Implement the Method in apiwrap.cpp
Open Telegram/SourceFiles/apiwrap.cpp and implement the method, typically placing it near related functionality like requestWallPaper (implemented around lines 1085–1105).
void ApiWrap::requestFoo(
const MTPInputPeer &peer,
int limit,
FnMut<void(const Data::FooResult&&)> done,
Fn<void(const MTP::Error&)> fail) {
// Cancel any previous identical request to prevent race conditions
if (_fooRequestId) {
request(base::take(_fooRequestId)).cancel();
}
_fooRequestId = request(MTPmessages_GetFoo(
MTP_flags(0),
peer,
MTP_int(limit)
))
.done([=](const MTPmessages_Foo &result) {
_fooRequestId = 0;
// Convert raw MTP object to domain object
auto foo = Data::FooResult::Create(_session, result);
if (done) done(std::move(foo));
})
.fail([=](const MTP::Error &error) {
_fooRequestId = 0;
if (fail) fail(error);
})
.send();
}
Key implementation patterns observed in the source include:
- Cancel-previous-request logic: Check the stored request ID and cancel existing requests before issuing new ones
- Request ID storage: Assign the return value of
request()to_fooRequestIdfor lifecycle management - Result conversion: Use
Data::FooResult::Create()to transformMTPmessages_Foointo internal data structures - Error propagation: Forward
MTP::Errorobjects through the fail callback - Send invocation: Chain
.send()to dispatch the request to the network layer
4. Create the Domain Data Model
If the method returns new object types, define a corresponding Data class. Create FooResult.h in the appropriate Telegram/Data/ subdirectory following existing patterns like Data::WallPaper.
class FooResult {
public:
static FooResult Create(
not_null<Main::Session*> session,
const MTPmessages_Foo &mtproto) {
FooResult result;
// Parse mtproto fields into result members
return result;
}
// Accessors for parsed data
};
5. Integrate and Test
Hook the result into the application layer by emitting signals or calling update methods on existing Data controllers. If the result influences UI components, expose reactive streams (rpl::producer) for live data sources.
Add unit tests under Telegram/Tests/ that mock the MTP response and verify that ApiWrap::requestFoo invokes callbacks with correctly parsed Data::FooResult objects.
Rebuild the project to regenerate MTProto headers and compile the new implementation:
cmake --build out --config Debug --target Telegram
Summary
- Modify
Telegram/SourceFiles/mtproto/scheme/api.tlto define the new TL constructor and trigger code generation - Declare the wrapper method in
Telegram/SourceFiles/apiwrap.husing Qt-friendly types andFnMutcallbacks - Implement the request logic in
Telegram/SourceFiles/apiwrap.cppwith proper request ID management and error handling - Create domain objects in
Telegram/Data/to encapsulate MTProto responses away from UI code - Test integration through unit tests and manual verification before committing
Frequently Asked Questions
What is the purpose of the _fooRequestId member variable?
The request ID stores the handle returned by the request() call, allowing you to cancel in-flight requests if the same method is called again before completion. This prevents race conditions where an older network response might overwrite newer data.
Do I need to manually edit the generated MTProto headers?
No. The TL code generator automatically creates C++ wrappers like MTPmessages_GetFoo when you rebuild the project after modifying api.tl. Manual changes to generated files will be overwritten on subsequent builds.
Why use FnMut<void(const Data::FooResult&&)> instead of raw MTP types?
The ApiWrap layer abstracts MTProto serialization details from UI components. By converting MTPmessages_Foo to Data::FooResult in the .done() handler, you provide type-safe, immutable domain objects that the rest of the application can use without importing MTProto headers.
Where should I place the Data model class for new API results?
Create new Data classes in Telegram/Data/ following the existing directory structure. If the result relates to messages, place it near HistoryItem or Message classes. Ensure the class provides a static Create factory method that accepts not_null<Main::Session*> and the raw MTP type, mirroring patterns in Data::WallPaper or Data::Chat.
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 →