1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include <td/telegram/Client.h>
8 #include <td/telegram/td_api.h>
9 #include <td/telegram/td_api.hpp>
10
11 #include <cstdint>
12 #include <functional>
13 #include <iostream>
14 #include <map>
15 #include <memory>
16 #include <sstream>
17 #include <string>
18 #include <vector>
19
20 // Simple single-threaded example of TDLib usage.
21 // Real world programs should use separate thread for the user input.
22 // Example includes user authentication, receiving updates, getting chat list and sending text messages.
23
24 // overloaded
25 namespace detail {
26 template <class... Fs>
27 struct overload;
28
29 template <class F>
30 struct overload<F> : public F {
overloaddetail::overload31 explicit overload(F f) : F(f) {
32 }
33 };
34 template <class F, class... Fs>
35 struct overload<F, Fs...>
36 : public overload<F>
37 , public overload<Fs...> {
overloaddetail::overload38 overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) {
39 }
40 using overload<F>::operator();
41 using overload<Fs...>::operator();
42 };
43 } // namespace detail
44
45 template <class... F>
overloaded(F...f)46 auto overloaded(F... f) {
47 return detail::overload<F...>(f...);
48 }
49
50 namespace td_api = td::td_api;
51
52 class TdExample {
53 public:
TdExample()54 TdExample() {
55 td::ClientManager::execute(td_api::make_object<td_api::setLogVerbosityLevel>(1));
56 client_manager_ = std::make_unique<td::ClientManager>();
57 client_id_ = client_manager_->create_client_id();
58 send_query(td_api::make_object<td_api::getOption>("version"), {});
59 }
60
loop()61 void loop() {
62 while (true) {
63 if (need_restart_) {
64 restart();
65 } else if (!are_authorized_) {
66 process_response(client_manager_->receive(10));
67 } else {
68 std::cout << "Enter action [q] quit [u] check for updates and request results [c] show chats [m <chat_id> "
69 "<text>] send message [me] show self [l] logout: "
70 << std::endl;
71 std::string line;
72 std::getline(std::cin, line);
73 std::istringstream ss(line);
74 std::string action;
75 if (!(ss >> action)) {
76 continue;
77 }
78 if (action == "q") {
79 return;
80 }
81 if (action == "u") {
82 std::cout << "Checking for updates..." << std::endl;
83 while (true) {
84 auto response = client_manager_->receive(0);
85 if (response.object) {
86 process_response(std::move(response));
87 } else {
88 break;
89 }
90 }
91 } else if (action == "close") {
92 std::cout << "Closing..." << std::endl;
93 send_query(td_api::make_object<td_api::close>(), {});
94 } else if (action == "me") {
95 send_query(td_api::make_object<td_api::getMe>(),
96 [this](Object object) { std::cout << to_string(object) << std::endl; });
97 } else if (action == "l") {
98 std::cout << "Logging out..." << std::endl;
99 send_query(td_api::make_object<td_api::logOut>(), {});
100 } else if (action == "m") {
101 std::int64_t chat_id;
102 ss >> chat_id;
103 ss.get();
104 std::string text;
105 std::getline(ss, text);
106
107 std::cout << "Sending message to chat " << chat_id << "..." << std::endl;
108 auto send_message = td_api::make_object<td_api::sendMessage>();
109 send_message->chat_id_ = chat_id;
110 auto message_content = td_api::make_object<td_api::inputMessageText>();
111 message_content->text_ = td_api::make_object<td_api::formattedText>();
112 message_content->text_->text_ = std::move(text);
113 send_message->input_message_content_ = std::move(message_content);
114
115 send_query(std::move(send_message), {});
116 } else if (action == "c") {
117 std::cout << "Loading chat list..." << std::endl;
118 send_query(td_api::make_object<td_api::getChats>(nullptr, 20), [this](Object object) {
119 if (object->get_id() == td_api::error::ID) {
120 return;
121 }
122 auto chats = td::move_tl_object_as<td_api::chats>(object);
123 for (auto chat_id : chats->chat_ids_) {
124 std::cout << "[chat_id:" << chat_id << "] [title:" << chat_title_[chat_id] << "]" << std::endl;
125 }
126 });
127 }
128 }
129 }
130 }
131
132 private:
133 using Object = td_api::object_ptr<td_api::Object>;
134 std::unique_ptr<td::ClientManager> client_manager_;
135 std::int32_t client_id_{0};
136
137 td_api::object_ptr<td_api::AuthorizationState> authorization_state_;
138 bool are_authorized_{false};
139 bool need_restart_{false};
140 std::uint64_t current_query_id_{0};
141 std::uint64_t authentication_query_id_{0};
142
143 std::map<std::uint64_t, std::function<void(Object)>> handlers_;
144
145 std::map<std::int64_t, td_api::object_ptr<td_api::user>> users_;
146
147 std::map<std::int64_t, std::string> chat_title_;
148
restart()149 void restart() {
150 client_manager_.reset();
151 *this = TdExample();
152 }
153
send_query(td_api::object_ptr<td_api::Function> f,std::function<void (Object)> handler)154 void send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler) {
155 auto query_id = next_query_id();
156 if (handler) {
157 handlers_.emplace(query_id, std::move(handler));
158 }
159 client_manager_->send(client_id_, query_id, std::move(f));
160 }
161
process_response(td::ClientManager::Response response)162 void process_response(td::ClientManager::Response response) {
163 if (!response.object) {
164 return;
165 }
166 //std::cout << response.request_id << " " << to_string(response.object) << std::endl;
167 if (response.request_id == 0) {
168 return process_update(std::move(response.object));
169 }
170 auto it = handlers_.find(response.request_id);
171 if (it != handlers_.end()) {
172 it->second(std::move(response.object));
173 handlers_.erase(it);
174 }
175 }
176
get_user_name(std::int64_t user_id) const177 std::string get_user_name(std::int64_t user_id) const {
178 auto it = users_.find(user_id);
179 if (it == users_.end()) {
180 return "unknown user";
181 }
182 return it->second->first_name_ + " " + it->second->last_name_;
183 }
184
get_chat_title(std::int64_t chat_id) const185 std::string get_chat_title(std::int64_t chat_id) const {
186 auto it = chat_title_.find(chat_id);
187 if (it == chat_title_.end()) {
188 return "unknown chat";
189 }
190 return it->second;
191 }
192
process_update(td_api::object_ptr<td_api::Object> update)193 void process_update(td_api::object_ptr<td_api::Object> update) {
194 td_api::downcast_call(
195 *update, overloaded(
196 [this](td_api::updateAuthorizationState &update_authorization_state) {
197 authorization_state_ = std::move(update_authorization_state.authorization_state_);
198 on_authorization_state_update();
199 },
200 [this](td_api::updateNewChat &update_new_chat) {
201 chat_title_[update_new_chat.chat_->id_] = update_new_chat.chat_->title_;
202 },
203 [this](td_api::updateChatTitle &update_chat_title) {
204 chat_title_[update_chat_title.chat_id_] = update_chat_title.title_;
205 },
206 [this](td_api::updateUser &update_user) {
207 auto user_id = update_user.user_->id_;
208 users_[user_id] = std::move(update_user.user_);
209 },
210 [this](td_api::updateNewMessage &update_new_message) {
211 auto chat_id = update_new_message.message_->chat_id_;
212 std::string sender_name;
213 td_api::downcast_call(*update_new_message.message_->sender_,
214 overloaded(
215 [this, &sender_name](td_api::messageSenderUser &user) {
216 sender_name = get_user_name(user.user_id_);
217 },
218 [this, &sender_name](td_api::messageSenderChat &chat) {
219 sender_name = get_chat_title(chat.chat_id_);
220 }));
221 std::string text;
222 if (update_new_message.message_->content_->get_id() == td_api::messageText::ID) {
223 text = static_cast<td_api::messageText &>(*update_new_message.message_->content_).text_->text_;
224 }
225 std::cout << "Got message: [chat_id:" << chat_id << "] [from:" << sender_name << "] [" << text
226 << "]" << std::endl;
227 },
228 [](auto &update) {}));
229 }
230
create_authentication_query_handler()231 auto create_authentication_query_handler() {
232 return [this, id = authentication_query_id_](Object object) {
233 if (id == authentication_query_id_) {
234 check_authentication_error(std::move(object));
235 }
236 };
237 }
238
on_authorization_state_update()239 void on_authorization_state_update() {
240 authentication_query_id_++;
241 td_api::downcast_call(
242 *authorization_state_,
243 overloaded(
244 [this](td_api::authorizationStateReady &) {
245 are_authorized_ = true;
246 std::cout << "Got authorization" << std::endl;
247 },
248 [this](td_api::authorizationStateLoggingOut &) {
249 are_authorized_ = false;
250 std::cout << "Logging out" << std::endl;
251 },
252 [this](td_api::authorizationStateClosing &) { std::cout << "Closing" << std::endl; },
253 [this](td_api::authorizationStateClosed &) {
254 are_authorized_ = false;
255 need_restart_ = true;
256 std::cout << "Terminated" << std::endl;
257 },
258 [this](td_api::authorizationStateWaitCode &) {
259 std::cout << "Enter authentication code: " << std::flush;
260 std::string code;
261 std::cin >> code;
262 send_query(td_api::make_object<td_api::checkAuthenticationCode>(code),
263 create_authentication_query_handler());
264 },
265 [this](td_api::authorizationStateWaitRegistration &) {
266 std::string first_name;
267 std::string last_name;
268 std::cout << "Enter your first name: " << std::flush;
269 std::cin >> first_name;
270 std::cout << "Enter your last name: " << std::flush;
271 std::cin >> last_name;
272 send_query(td_api::make_object<td_api::registerUser>(first_name, last_name),
273 create_authentication_query_handler());
274 },
275 [this](td_api::authorizationStateWaitPassword &) {
276 std::cout << "Enter authentication password: " << std::flush;
277 std::string password;
278 std::getline(std::cin, password);
279 send_query(td_api::make_object<td_api::checkAuthenticationPassword>(password),
280 create_authentication_query_handler());
281 },
282 [this](td_api::authorizationStateWaitOtherDeviceConfirmation &state) {
283 std::cout << "Confirm this login link on another device: " << state.link_ << std::endl;
284 },
285 [this](td_api::authorizationStateWaitPhoneNumber &) {
286 std::cout << "Enter phone number: " << std::flush;
287 std::string phone_number;
288 std::cin >> phone_number;
289 send_query(td_api::make_object<td_api::setAuthenticationPhoneNumber>(phone_number, nullptr),
290 create_authentication_query_handler());
291 },
292 [this](td_api::authorizationStateWaitEncryptionKey &) {
293 std::cout << "Enter encryption key or DESTROY: " << std::flush;
294 std::string key;
295 std::getline(std::cin, key);
296 if (key == "DESTROY") {
297 send_query(td_api::make_object<td_api::destroy>(), create_authentication_query_handler());
298 } else {
299 send_query(td_api::make_object<td_api::checkDatabaseEncryptionKey>(std::move(key)),
300 create_authentication_query_handler());
301 }
302 },
303 [this](td_api::authorizationStateWaitTdlibParameters &) {
304 auto parameters = td_api::make_object<td_api::tdlibParameters>();
305 parameters->database_directory_ = "tdlib";
306 parameters->use_message_database_ = true;
307 parameters->use_secret_chats_ = true;
308 parameters->api_id_ = 94575;
309 parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
310 parameters->system_language_code_ = "en";
311 parameters->device_model_ = "Desktop";
312 parameters->application_version_ = "1.0";
313 parameters->enable_storage_optimizer_ = true;
314 send_query(td_api::make_object<td_api::setTdlibParameters>(std::move(parameters)),
315 create_authentication_query_handler());
316 }));
317 }
318
check_authentication_error(Object object)319 void check_authentication_error(Object object) {
320 if (object->get_id() == td_api::error::ID) {
321 auto error = td::move_tl_object_as<td_api::error>(object);
322 std::cout << "Error: " << to_string(error) << std::flush;
323 on_authorization_state_update();
324 }
325 }
326
next_query_id()327 std::uint64_t next_query_id() {
328 return ++current_query_id_;
329 }
330 };
331
main()332 int main() {
333 TdExample example;
334 example.loop();
335 }
336