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