#include "tgclient.h" #include #include #include #include #include #include "config.h" #include "typeconverter.h" namespace pl = std::placeholders; // overloaded namespace detail { template struct overload; template struct overload : public F { explicit overload(F f) : F(f) { } }; template struct overload : public overload , overload { overload(F f, Fs... fs) : overload(f), overload(fs...) { } using overload::operator(); using overload::operator(); }; } // namespace detail template auto overloaded(F... f) { return detail::overload(f...); } class Placeholder : public td_api::Object { public: static const std::int32_t ID = 0; }; const uint32_t TGClient::MAXCACHEDUSERCOUNT = 1024; const uint64_t TGClient::CACHEDUSERCOUNTCLEANUPTIME = 86400; void(TGClient::* TGClient::STATICHANDLERS [])(objptr) = { nullptr, // ids with 0 are handled with the authorisation handler nullptr, // zero handle &TGClient::loadInitInternalCallback, }; static const uint64_t HANDLER_NULL = 1; TGClient::TGClient(std::function initDoneCallback) : initDoneCallback(initDoneCallback) { td::ClientManager::execute(td_api::make_object(1)); client_manager_ = std::make_unique(); client_id_ = client_manager_->create_client_id(); send_query(td_api::make_object("version")); } void TGClient::setAuthData(std::function getAuthCodeCallback_) { getAuthCodeCallback = getAuthCodeCallback_; } void TGClient::loop() { shouldrun = true; while (shouldrun) { if (need_restart_) { restart(); } else if (!are_authorized_) { Log::warn << "not authorized"; while(!are_authorized_) { process_response(client_manager_->receive(1)); } } else { //process updates while(true) { auto response = client_manager_->receive(0.1); if (response.object) process_response(std::move(response)); else break; } } } Log::note << "Client Loop terminated"; } void TGClient::stop() { shouldrun = false; send_query(td_api::make_object()); } void TGClient::registerNewMessageHandler(MessageCallback mclb) { messageCallback = mclb; } void TGClient::registerStatusUpdateHandler(StatusCallback sclb) { statusCallback = sclb; } void TGClient::registerDraftHandler(DraftCallback dclb) { draftCallback = dclb; } void TGClient::registerActionHandler(ActionCallback clb) { actionCallback = clb; } void TGClient::registerDeleteHandler(DeleteCallback dclb) { deleteCallback = dclb; } void TGClient::registerUserUpdateHandler(UserUpdateCallback uuclb) { userupdCallback = uuclb; } void TGClient::registerEditMessageHandler(EditMessageCallback emclb) { editMessageCallback = emclb; } void TGClient::registerIndexDoneHandle(IndexDoneCallback idclb) { indexDoneCallback = idclb; } void TGClient::registerFileUpdateCallback(FileUpdateCallback fuclb) { fileUpdateCallback = fuclb; } void TGClient::registerNewChatCallback(NewChatCallback ncclb) { newChatCallback = ncclb; } void TGClient::registerChatFiltersCallback(ChatFiltersCallback cfclb) { chatFiltersCallback = cfclb; } static auto createFormattedText(const std::string& in, std::vector>& entities) { auto formattedtext = td::make_tl_object(); formattedtext->text_ = in; formattedtext->entities_.reserve(entities.size()); for(uint32_t i = 0; i < entities.size(); ++i) { auto& ref = entities.at(i); formattedtext->entities_.push_back(td::make_tl_object(ref->offset_, ref->length_, std::move(ref->type_))); } return formattedtext; } static auto createMessageInputTextEntities(const std::string& in, std::vector>& entities) { auto formattedText = createFormattedText(in, entities); return td::make_tl_object(std::move(formattedText), false, false); } static auto createMessageInputText(const std::string& in) { std::vector> entities; return createMessageInputTextEntities(in, entities); } void TGClient::openChat(int64_t chatid) { send_staticquery(td::make_tl_object(chatid), HANDLER_NULL); } void TGClient::closeChat(int64_t chatid) { send_staticquery(td::make_tl_object(chatid), HANDLER_NULL); } void TGClient::deleteMessage(int64_t chatid, int64_t messageid, bool forall) { send_staticquery(td::make_tl_object(chatid, td_api::array(1, messageid), forall), HANDLER_NULL); } void TGClient::screenShottaken(int64_t chatid) { td_api::array messageIds; send_staticquery(td::make_tl_object(chatid, std::move(messageIds), td::make_tl_object(), false), HANDLER_NULL); } void TGClient::reply(const objptr& rplyto, const std::string& awnser) { reply(rplyto->chat_id_, rplyto->id_, awnser); } void TGClient::reply(int64_t chat, int64_t msg, const std::string& awnser) { auto inputmsgCont = createMessageInputText(awnser); auto sendMsg = td::make_tl_object(); sendMsg->chat_id_ = chat; sendMsg->reply_to_ = td_api::make_object(chat, msg); sendMsg->input_message_content_ = std::move(inputmsgCont); send_staticquery(std::move(sendMsg), HANDLER_NULL); } void TGClient::editMessage(const objptr& toEdit, const std::string& newText) { std::vector> entities; editMessage(toEdit, newText, entities); } void TGClient::editMessage(const objptr& toEdit, const std::string& newText, std::vector>& entities) { auto editMsg = td::make_tl_object(); editMsg->chat_id_ = toEdit->chat_id_; editMsg->message_id_ = toEdit->id_; editMsg->input_message_content_ = createMessageInputTextEntities(newText, entities); send_staticquery(std::move(editMsg), HANDLER_NULL); } void TGClient::editMessageCaption(const objptr& toEdit, const std::string& newText, std::vector>& entities ) { auto editMsg = td::make_tl_object(); editMsg->chat_id_ = toEdit->chat_id_; editMsg->message_id_ = toEdit->id_; editMsg->caption_ = createFormattedText(newText, entities); send_staticquery(std::move(editMsg), HANDLER_NULL); } void TGClient::sendCallbackQuery(int64_t chat, int64_t messageid, const std::string& payload) { Log::info << "sendCallbackQuery: " << chat << " " << messageid << " " << payload; auto payloadobj = td::make_tl_object(); payloadobj->data_ = payload; auto sendCallback = td::make_tl_object(); sendCallback->chat_id_ = chat; sendCallback->message_id_ = messageid; sendCallback->payload_ = std::move(payloadobj); send_wrappedquery(std::move(sendCallback), [](objptr clb) { Log::info << "CallbackAwnser - text: " << clb->text_ << " showAlert: " << clb->show_alert_ << " url: " << clb->url_; }); } void TGClient::muteChat(int64_t chatid, int32_t mutefor) { auto settings = td::make_tl_object(); settings->use_default_mute_for_ = false; settings->mute_for_ = mutefor; settings->use_default_sound_ = true; settings->use_default_show_preview_ = true; settings->use_default_disable_pinned_message_notifications_ = true; settings->use_default_disable_mention_notifications_ = true; send_staticquery(td::make_tl_object(chatid, std::move(settings)), HANDLER_NULL); } void TGClient::addChatToChatListFilter(int64_t chatid, int32_t chatfilterid) { send_staticquery(td::make_tl_object(chatid, td::make_tl_object(chatfilterid)), HANDLER_NULL); } void TGClient::indexChat(int64_t chatid) { requestMessages(chatid, 0, indexDoneCallback, editMessageCallback); } void TGClient::getAllTextMessages(int64_t chatid, std::function)> f) { if(!f) return; // only works without multithreading! // with multithreading onDone could be processed before the last update -> segfault auto map = new std::map(); requestMessages(chatid, 0, [this, map, f](int64_t chat) { f(*map); delete map; }, [this, map](objptr msg) { // try to get text bool isText = false; // text does not need to be freed, because it has a owning pointer in msg td_api::formattedText* text = getMessageFormattedText(*msg, isText); if(msg->sender_id_->get_id() == td_api::messageSenderUser::ID) { auto sender = (td_api::messageSenderUser*) (msg->sender_id_).get(); if(sender->user_id_ != me) { Log::warn << "ignore non-me-config-message from: " << sender->user_id_; return; } if(text) { (*map)[msg->id_] = text->text_; } } }); } void TGClient::getLastMessages(int64_t chatid, std::function>&)> f) { std::vector>* vec = new std::vector>(); requestMessages(chatid, 0, [vec, f](int64_t chat) { f(*vec); delete vec; }, [vec](objptr msg) { std::shared_ptr msgOut = std::make_shared(); convertMessage(*msg, *msgOut); vec->push_back(std::move(msgOut)); }, false); } void TGClient::downloadFile(int32_t file_id) { Log::info << "start download for file: " << file_id; send_staticquery(td::make_tl_object(file_id, /* prio */ 15, 0, 0, false), HANDLER_NULL); } void TGClient::sendTextMessageToSelf(const std::string& text) { auto sendmsg = td::make_tl_object(); sendmsg->chat_id_ = me; sendmsg->message_thread_id_ = 0; sendmsg->reply_to_ = nullptr; sendmsg->options_ = nullptr; sendmsg->reply_markup_ = nullptr; std::vector> entities; sendmsg->input_message_content_ = createMessageInputTextEntities(text, entities); send_staticquery(std::move(sendmsg), HANDLER_NULL); } void TGClient::getMessage(int64_t chatid, int64_t messageid, std::function)> f) { send_wrappedquery(td::make_tl_object(chatid, messageid), f); } void TGClient::setDraft(int64_t chat, const std::string& text, int64_t replyto, int64_t messageThread) { auto itext = createMessageInputText(text); objptr dmsg = td_api::make_object(replyto, time(nullptr), std::move(itext)); send_staticquery(td_api::make_object(chat, messageThread, std::move(dmsg)), HANDLER_NULL); drafts.erase(chat); //if there was a user draft, its now gone } void TGClient::clearDraft(int64_t chat, int64_t messageThread) { objptr dmsg; send_staticquery(td_api::make_object(chat, messageThread, std::move(dmsg)), HANDLER_NULL); drafts.erase(chat); } bool TGClient::hasDraft(int64_t chat) const { return drafts.find(chat) != drafts.end(); } const User* TGClient::getCachedUser(int64_t userid) { auto it = usercache.find(userid); if(it == usercache.end()) { // no user found return nullptr; } //update accesstimes accessUser(it); return &it->second; } void TGClient::accessUser(std::map::iterator it) { //change last access time uint64_t lastacc = it->second.lastaccessed; //remove old entry auto it2 = usercache_timeout.lower_bound(lastacc); auto it2end = usercache_timeout.upper_bound(lastacc); for( ;it2 != it2end; ++it2) { if(it2->second == it->first) { usercache_timeout.erase(it2); break; } } uint64_t newacc = time(0); //insert new value usercache_timeout.insert({newacc, it->first}); it->second.lastaccessed = newacc; } void TGClient::addUser(CachedUser& u) { //check for existing user auto it = usercache.find(u.tgid); if(it != usercache.end()) { //remove old user removeUser(it); } //add user uint64_t accesstime = time(0); u.lastaccessed = accesstime; usercache.insert({u.tgid, u}); //add timing information usercache_timeout.insert({accesstime, u.tgid}); //remove old entrys checkUserCache(); } void TGClient::removeUser(std::map::iterator it) { if(it == usercache.end()) return; uint64_t accesstime = it->second.lastaccessed; int64_t id = it->second.tgid; //remove from timeout cache auto it2 = usercache_timeout.lower_bound(accesstime); auto it2end = usercache_timeout.upper_bound(accesstime); for( ;it2 != it2end; ++it2) { if(it2->second == id) { usercache_timeout.erase(it2); break; } } //remove from real cache usercache.erase(it); } void TGClient::checkUserCache() { if(usercache.size() > MAXCACHEDUSERCOUNT) { uint64_t lastallowed = time(0) - CACHEDUSERCOUNTCLEANUPTIME; auto it = usercache_timeout.begin(); auto itend = usercache_timeout.upper_bound(lastallowed); while(it != itend) { if(it->first == (uint64_t) me) { ++it; continue; // never remove self from cache } auto itcopy = it; ++itcopy; auto todeleteit = usercache.find(it->second); removeUser(todeleteit); it = itcopy; } } } void TGClient::requestMessages(int64_t chatid, int64_t from_message_id, std::function onDone, std::function)> forMessage, bool recurse) { Log::debug << "requestMessages " << chatid << " from: " << from_message_id; send_wrappedquery(td_api::make_object(chatid, from_message_id, 0, 100, false), [this, chatid, onDone, forMessage, recurse](objptr m) { //request next chunk td_api::array>& arr = m->messages_; Log::trace << "got " << arr.size() << " messages"; if(arr.size() == 0) { // indexing done Log::info << "Chatindex done"; if(onDone) { onDone(chatid); } return; } //issue next request bevore processing -> requires reverse iteration to find the last message id int64_t smallestid = std::numeric_limits::max(); for(td_api::array>::const_reverse_iterator it = arr.rbegin(); it != arr.rend(); ++it) { const td_api::object_ptr& obj = *it; if(obj) { int64_t id = obj->id_; if(id < smallestid) { smallestid = id; break; //the first element should be good, because they should be ordered } } } //start next request if(shouldrun && smallestid != std::numeric_limits::max() && recurse) { requestMessages(chatid, smallestid, onDone, forMessage); } //process the messages if(forMessage) { for(auto it = arr.begin(); it != arr.end(); ++it) { if(*it) { forMessage(std::move(*it)); } } } if(!recurse && onDone) { onDone(chatid); } }); } void TGClient::setOption(const std::string& name, objptr val) { client_manager_->execute(td_api::make_object(name, std::move(val))); } void TGClient::setOptions() { setOption("disable_persistent_network_statistics", td_api::make_object(true)); setOption("disable_sent_scheduled_message_notifications", td_api::make_object(true)); setOption("disable_time_adjustment_protection", td_api::make_object(true)); setOption("disable_top_chats", td_api::make_object(true)); setOption("ignore_background_updates", td_api::make_object(false)); setOption("ignore_inline_thumbnails", td_api::make_object(true)); setOption("ignore_platform_restrictions", td_api::make_object(true)); } void TGClient::loadInit() { send_wrappedquery(td_api::make_object(), [this](objptr meu) { CachedUser cachedme; convertUser(*meu, cachedme); me = cachedme.tgid; addUser(cachedme); //load chat list td_api::object_ptr mainlist = td_api::make_object(); send_staticquery(td_api::make_object(std::move(mainlist), std::numeric_limits::max()), 2); }); } void TGClient::loadInitInternalCallback(Object o) { (void) o; Log::note << "init done, chats loaded"; if(initDoneCallback) initDoneCallback(); initDone = true; } void TGClient::restart() { client_manager_.reset(); authorization_state_.reset(); (this)->~TGClient(); new (this) TGClient(); } void TGClient::send_query(td_api::object_ptr f, std::function handler) { auto query_id = next_query_id(); if (handler) { handlers_.emplace(query_id, std::move(handler)); } client_manager_->send(client_id_, query_id, std::move(f)); } void TGClient::send_staticquery(td_api::object_ptr f, uint64_t handlerid) { assert(handlerid < STATICHANDLERCOUNT); client_manager_->send(client_id_, handlerid, std::move(f)); } template void TGClient::send_wrappedquery(td_api::object_ptr f, std::function)> handler, std::function onError, bool printError) { send_query(std::move(f), [handler, onError, printError](Object o) { if(catchErrors(o, onError, printError) && handler) { auto casted = td::move_tl_object_as(o); handler(std::move(casted)); } }); } template bool TGClient::catchErrors(const Object& o, std::function onError, bool printError) { if(!o) return false; if(o->get_id() == td_api::error::ID) { if(printError) { const objptr& err = (const objptr&) o; Log::warn << "error: " << err->code_ << " " << err->message_; } if(onError) onError(); return false; } if(T::ID != Placeholder::ID && o->get_id() != T::ID) { if(printError) Log::warn << "function did not return required type: returned_id: " << o->get_id() << " required_id: " << T::ID; if(onError) onError(); return false; } return true; } template void TGClient::send_inplace(Args... args, std::function)> handler, bool printError) { send_wrappedquery(td_api::make_object(std::forward(args)...), handler, printError); } void TGClient::process_response(td::ClientManager::Response response) { if (!response.object) { return; } // update if (response.request_id == 0) { return process_update(std::move(response.object)); } if(response.request_id < STATICHANDLERCOUNT) { auto handler = STATICHANDLERS[response.request_id]; if(!catchErrors(response.object) || !handler) return; // nullptr handler (this->*handler)(std::move(response.object)); return; } auto it = handlers_.find(response.request_id); if (it != handlers_.end()) { it->second(std::move(response.object)); handlers_.erase(it); } } void TGClient::process_update(td_api::object_ptr update) { td_api::downcast_call( *update, overloaded( [this](td_api::updateAuthorizationState& update_authorization_state) { authorization_state_ = std::move(update_authorization_state.authorization_state_); on_authorization_state_update(); }, [this](td_api::updateNewMessage& update_new_message) { if(messageCallback) { messageCallback(std::move(update_new_message.message_)); } }, [this](td_api::updateUserStatus& updateStatus) { if(statusCallback) statusCallback(updateStatus); }, [this](td_api::updateChatDraftMessage& draft) { if(draftCallback) { const objptr& draftmsg = draft.draft_message_; std::string text; if(draftmsg) { const objptr& imc = draftmsg->input_message_text_; if(imc->get_id() == td_api::inputMessageText::ID) { text = ((const td_api::inputMessageText&) *imc).text_->text_; } } draftCallback(draft.chat_id_, text); } }, [this](td_api::updateDeleteMessages& dele) { if(deleteCallback && dele.is_permanent_ && !dele.from_cache_) { int64_t chatid = dele.chat_id_; const std::vector& list = dele.message_ids_; //trigger callback for(int64_t msgid : list) { deleteCallback(chatid, msgid); } } }, [this](td_api::updateMessageEdited& edit) { if(editMessageCallback) { //request full message send_wrappedquery(td_api::make_object(edit.chat_id_, edit.message_id_), editMessageCallback); } }, [this](td_api::updateChatAction& action) { if(actionCallback) { int64_t chatid = action.chat_id_, userid = action.chat_id_; ChatAction::ChatAction act = ChatAction::getAction(action.action_->get_id()); actionCallback(chatid, userid, act); } }, [this](td_api::updateUser& user) { if(userupdCallback) { const td_api::user& ou = *user.user_; CachedUser u; convertUser(ou, u); //cache user addUser(u); //trigger event userupdCallback(u); } }, [this](td_api::updateFile& fileupd) { if(fileUpdateCallback) { fileUpdateCallback(std::move(fileupd.file_)); } }, [this](td_api::updateNewChat& newChat) { if(newChatCallback) { newChatCallback(std::move(newChat.chat_)); } }, [this](td_api::updateChatFolders& chatFilters) { if(chatFiltersCallback) { std::map out; auto& arr = chatFilters.chat_folders_; for(auto& it : arr) { out[it->id_] = it->title_; } chatFiltersCallback(out); } }, [](auto& u) {})); //default } auto TGClient::create_authentication_query_handler() { return [this, id = authentication_query_id_](Object object) { if (id == authentication_query_id_) { check_authentication_error(std::move(object)); } }; } void TGClient::on_authorization_state_update() { Log::trace << "update auth state " << authorization_state_->get_id(); authentication_query_id_++; td_api::downcast_call( *authorization_state_, overloaded( [this](td_api::authorizationStateReady&) { are_authorized_ = true; Log::note << "Got authorization"; loadInit(); }, [this](td_api::authorizationStateLoggingOut&) { are_authorized_ = false; std::cout << "Logging out" << std::endl; }, [this](td_api::authorizationStateClosing&) { std::cout << "Closing" << std::endl; }, [this](td_api::authorizationStateClosed&) { are_authorized_ = false; need_restart_ = true; std::cout << "Terminated" << std::endl; }, [this](td_api::authorizationStateWaitCode&) { if(!getAuthCodeCallback) { Log::error << "no authCode Callback registered"; return; } std::string code = getAuthCodeCallback(); send_query(td_api::make_object(code), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitRegistration&) { std::string first_name; std::string last_name; std::cout << "Enter your first name: " << std::flush; std::cin >> first_name; std::cout << "Enter your last name: " << std::flush; std::cin >> last_name; send_query(td_api::make_object(first_name, last_name), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitPassword&) { send_query(td_api::make_object(Config::config.tgCloudPassword), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitOtherDeviceConfirmation& state) { std::cout << "Confirm this login link on another device: " << state.link_ << std::endl; }, [this](td_api::authorizationStateWaitPhoneNumber&) { if(Config::config.phoneNumber.empty()) { Log::fatal << "empty phone number"; return; } send_query(td_api::make_object(Config::config.phoneNumber, nullptr), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitEmailAddress&) { send_query(td_api::make_object(""), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitEmailCode&) { send_query(td_api::make_object(""), create_authentication_query_handler()); }, [this](td_api::authorizationStateWaitTdlibParameters&) { auto parameters = td_api::make_object(); parameters->database_directory_ = "tdlib"; parameters->use_test_dc_ = false; parameters->use_file_database_ = true; parameters->use_chat_info_database_ = true; parameters->use_message_database_ = true; parameters->use_secret_chats_ = false; parameters->ignore_file_names_ = false; parameters->api_id_ = Config::config.apiid; parameters->api_hash_ = Config::config.apihash; Log::info << "using api: " << Config::config.apiid << " " << Config::config.apihash; parameters->system_language_code_ = "en"; parameters->device_model_ = "Desktop"; parameters->application_version_ = "1.0"; parameters->enable_storage_optimizer_ = true; send_query(std::move(parameters), create_authentication_query_handler()); setOptions(); })); } void TGClient::check_authentication_error(Object object) { if (object->get_id() == td_api::error::ID) { auto error = td::move_tl_object_as(object); std::cout << "Error: " << to_string(error) << std::flush; on_authorization_state_update(); } } std::uint64_t TGClient::next_query_id() { return ++current_query_id_; }