#ifndef _ACCOUNT_DATA_H #define _ACCOUNT_DATA_H #include "buildopt.h" #include "identifiers.h" #include "transceiver.h" #include #include #include #include #include #include #ifndef NoVoip #include #else namespace tgvoip { struct VoIPController {}; } #endif bool isPhoneNumber(const char *s); const char *getCanonicalPhoneNumber(const char *s); UserId purpleBuddyNameToUserId(const char *s); SecretChatId purpleBuddyNameToSecretChatId(const char *s); bool isPrivateChat(const td::td_api::chat &chat); UserId getUserIdByPrivateChat(const td::td_api::chat &chat); bool isChatInContactList(const td::td_api::chat &chat, const td::td_api::user *privateChatUser); BasicGroupId getBasicGroupId(const td::td_api::chat &chat); SupergroupId getSupergroupId(const td::td_api::chat &chat); SecretChatId getSecretChatId(const td::td_api::chat &chat); bool isGroupMember(const td::td_api::object_ptr &status); bool isSameUser(const td::td_api::MessageSender &member1, const td::td_api::MessageSender &member2); enum { CHAT_HISTORY_REQUEST_LIMIT = 50, CHAT_HISTORY_RETRIEVE_LIMIT = 100 }; class PendingRequest { public: uint64_t requestId; PendingRequest(uint64_t requestId) : requestId(requestId) {} virtual ~PendingRequest() {} }; class GroupInfoRequest: public PendingRequest { public: BasicGroupId groupId; GroupInfoRequest(uint64_t requestId, BasicGroupId groupId) : PendingRequest(requestId), groupId(groupId) {} }; class SupergroupInfoRequest: public PendingRequest { public: SupergroupId groupId; SupergroupInfoRequest(uint64_t requestId, SupergroupId groupId) : PendingRequest(requestId), groupId(groupId) {} }; class GroupMembersRequestCont: public PendingRequest { public: SupergroupId groupId; td::td_api::object_ptr members; GroupMembersRequestCont(uint64_t requestId, SupergroupId groupId, td::td_api::chatMembers *members) : PendingRequest(requestId), groupId(groupId), members(std::move(members)) {} }; class ContactRequest: public PendingRequest { public: std::string phoneNumber; std::string alias; std::string groupName; UserId userId; ContactRequest(uint64_t requestId, const std::string &phoneNumber, const std::string &alias, const std::string &groupName, UserId userId) : PendingRequest(requestId), phoneNumber(phoneNumber), alias(alias), groupName(groupName), userId(userId) {} }; class GroupJoinRequest: public PendingRequest { public: enum class Type { InviteLink, Username, }; std::string joinString; Type type; ChatId chatId; GroupJoinRequest(uint64_t requestId, const std::string &joinString, Type type, ChatId chatId = ChatId::invalid) : PendingRequest(requestId), joinString(joinString), type(type), chatId(chatId) {} }; class SendMessageRequest: public PendingRequest { public: ChatId chatId; std::string tempFile; SendMessageRequest(uint64_t requestId, ChatId chatId, const char *tempFile) : PendingRequest(requestId), chatId(chatId), tempFile(tempFile ? tempFile : "") {} }; class UploadRequest: public PendingRequest { public: PurpleXfer *xfer; ChatId chatId; UploadRequest(uint64_t requestId, PurpleXfer *xfer, ChatId chatId) : PendingRequest(requestId), xfer(xfer), chatId(chatId) {} }; struct TgMessageInfo { enum class Type { Photo, Sticker, Other }; MessageId id; Type type; std::string incomingGroupchatSender; time_t timestamp; bool outgoing; bool sentLocally = false; // For outgoing messages, whether sent by this very client MessageId repliedMessageId; td::td_api::object_ptr repliedMessage; std::string forwardedFrom; void assign(const TgMessageInfo &other) { id = other.id; type = other.type; incomingGroupchatSender = other.incomingGroupchatSender; timestamp = other.timestamp; outgoing = other.outgoing; sentLocally = other.sentLocally; repliedMessageId = other.repliedMessageId; repliedMessage = nullptr; forwardedFrom = other.forwardedFrom; } }; class PurpleTdClient; // Used for matching completed downloads to chats they belong to, and for starting PurpleXfer for // time-consuming downloads class DownloadRequest: public PendingRequest { public: ChatId chatId; // For inline downloads this is a copy of original TgMessageInfo from IncomingMessage. TgMessageInfo message; int32_t fileId; int32_t fileSize; int32_t downloadedSize; std::string fileDescription; int tempFd = -1; std::string tempFileName; td::td_api::object_ptr thumbnail; // Could not pass object_ptr through variadic funciton :( DownloadRequest(uint64_t requestId, ChatId chatId, TgMessageInfo &message, int32_t fileId, int32_t fileSize, const std::string &fileDescription, td::td_api::file *thumbnail) : PendingRequest(requestId), chatId(chatId), fileId(fileId), fileSize(fileSize), downloadedSize(0), fileDescription(fileDescription), thumbnail(thumbnail) { // If download is started while the message is in PendingMessageQueue, repliedMessage will // be on IncomingMessage, and one here in TgMessageInfo will be NULL. In this case, // repliedMessage will be moved onto DownloadRequest if message leaves PendingMessageQueue // before download is complete (meaning it took more than 1 second to download). this->message.assign(message); if (message.repliedMessage) this->message.repliedMessage = std::move(message.repliedMessage); } }; class AvatarDownloadRequest: public PendingRequest { public: UserId userId; ChatId chatId; AvatarDownloadRequest(uint64_t requestId, const td::td_api::user *user) : PendingRequest(requestId), userId(getId(*user)), chatId(ChatId::invalid) {} AvatarDownloadRequest(uint64_t requestId, const td::td_api::chat *chat) : PendingRequest(requestId), userId(UserId::invalid), chatId(getId(*chat)) {} }; class NewPrivateChatForMessage: public PendingRequest { public: std::string username; std::string message; PurpleXfer *fileUpload; NewPrivateChatForMessage(uint64_t requestId, const char *username, const char *message) : PendingRequest(requestId), username(username), message(message ? message : nullptr), fileUpload(nullptr) {} NewPrivateChatForMessage(uint64_t requestId, const char *username, PurpleXfer *upload) : PendingRequest(requestId), username(username), fileUpload(upload) {} }; class ChatActionRequest: public PendingRequest { public: enum class Type: uint8_t { Kick, Invite, GenerateInviteLink }; Type type; ChatId chatId; ChatActionRequest(uint64_t requestId, Type type, ChatId chatId) : PendingRequest(requestId), type(type), chatId(chatId) {} }; struct IncomingMessage { td::td_api::object_ptr message; td::td_api::object_ptr repliedMessage; td::td_api::object_ptr thumbnail; std::string inlineDownloadedFilePath; // This doesn't have to be a separate struct, it exists for historical reasons. // Could be refactored. TgMessageInfo messageInfo; int32_t selectedPhotoSizeId; unsigned inlineFileSizeLimit; bool standardDownloadConfigured; bool repliedMessageFetchDoneOrFailed; bool inlineDownloadComplete; bool inlineDownloadTimeout; bool animatedStickerConverted; bool animatedStickerConvertSuccess; int animatedStickerImageId; }; class PendingMessageQueue { public: enum class MessageAction { Append, Prepend }; static constexpr MessageAction Append = MessageAction::Append; static constexpr MessageAction Prepend = MessageAction::Prepend; using TdMessagePtr = td::td_api::object_ptr; IncomingMessage &addPendingMessage(IncomingMessage &&message, MessageAction action); void setMessageReady(ChatId chatId, MessageId messageId, std::vector &readyMessages); IncomingMessage addReadyMessage(IncomingMessage &&message, MessageAction action); IncomingMessage *findPendingMessage(ChatId chatId, MessageId messageId); void flush(std::vector &messages); void setChatNotReady(ChatId chatId); void setChatReady(ChatId chatId, std::vector &readyMessages); bool isChatReady(ChatId chatId); private: struct Message { IncomingMessage message; bool ready; }; struct ChatQueue { ChatId chatId; bool ready = true; std::list messages; }; std::vector m_queues; std::vector::iterator getChatQueue(ChatId chatId); Message &addMessage(ChatQueue &queue, MessageAction action); void extractReadyMessages(std::vector::iterator pQueue, std::vector &readyMessages); }; struct ReadReceipt { ChatId chatId; MessageId messageId; }; class TdAccountData { public: using TdUserPtr = td::td_api::object_ptr; using TdChatPtr = td::td_api::object_ptr; using TdGroupPtr = td::td_api::object_ptr; using TdGroupInfoPtr = td::td_api::object_ptr; using TdSupergroupPtr = td::td_api::object_ptr; using TdSupergroupInfoPtr = td::td_api::object_ptr; using TdChatMembersPtr = td::td_api::object_ptr; using SecretChatPtr = td::td_api::object_ptr; struct { unsigned maxCaptionLength = 0; unsigned maxMessageLength = 0; } options; PurpleAccount *const purpleAccount; TdTransceiver &transceiver; TdAccountData(PurpleAccount *purpleAccount, TdTransceiver &transceiver) : purpleAccount(purpleAccount), transceiver(transceiver) {} void updateUser(TdUserPtr user); void setUserStatus(UserId UserId, td::td_api::object_ptr status); void updateSmallProfilePhoto(UserId userId, td::td_api::object_ptr photo); void updateBasicGroup(TdGroupPtr group); void setBasicGroupInfoRequested(BasicGroupId groupId); bool isBasicGroupInfoRequested(BasicGroupId groupId); void updateBasicGroupInfo(BasicGroupId groupId, TdGroupInfoPtr groupInfo); void updateSupergroup(TdSupergroupPtr group); void setSupergroupInfoRequested(SupergroupId groupId); bool isSupergroupInfoRequested(SupergroupId groupId); void updateSupergroupInfo(SupergroupId groupId, TdSupergroupInfoPtr groupInfo); void updateSupergroupMembers(SupergroupId groupId, TdChatMembersPtr members); void addChat(TdChatPtr chat); // Updates existing chat if any void updateChatPosition(ChatId chatId, td::td_api::object_ptr &&position); void updateChatTitle(ChatId chatId, const std::string &title); void updateSmallChatPhoto(ChatId chatId, td::td_api::object_ptr photo); void setContacts(const td::td_api::users &users); void getContactsWithNoChat(std::vector &userIds); void getChats(std::vector &chats) const; void deleteChat(ChatId id); void addExpectedChat(ChatId id); bool isExpectedChat(ChatId chatId); void removeExpectedChat(ChatId id); const td::td_api::chat *getChat(ChatId chatId) const; int getPurpleChatId(ChatId tdChatId); const td::td_api::chat *getChatByPurpleId(int32_t purpleChatId) const; const td::td_api::chat *getPrivateChatByUserId(UserId userId) const; const td::td_api::user *getUser(UserId userId) const; const td::td_api::user *getUserByPhone(const char *phoneNumber) const; const td::td_api::user *getUserByPrivateChat(const td::td_api::chat &chat); std::string getDisplayName(const td::td_api::user &user) const; std::string getDisplayName(UserId userId) const; void getUsersByDisplayName(const char *displayName, std::vector &users); const td::td_api::basicGroup *getBasicGroup(BasicGroupId groupId) const; const td::td_api::basicGroupFullInfo *getBasicGroupInfo(BasicGroupId groupId) const; const td::td_api::supergroup *getSupergroup(SupergroupId groupId) const; const td::td_api::supergroupFullInfo *getSupergroupInfo(SupergroupId groupId) const; const td::td_api::chatMembers*getSupergroupMembers(SupergroupId groupId) const; const td::td_api::chat *getBasicGroupChatByGroup(BasicGroupId groupId) const; const td::td_api::chat *getSupergroupChatByGroup(SupergroupId groupId) const; bool isGroupChatWithMembership(const td::td_api::chat &chat) const; const td::td_api::chat *getChatBySecretChat(SecretChatId secretChatId); template void addPendingRequest(ArgsType... args) { m_requests.push_back(std::make_unique(args...)); } template void addPendingRequest(uint64_t requestId, std::unique_ptr &&request) { m_requests.push_back(std::move(request)); m_requests.back()->requestId = requestId; } template std::unique_ptr getPendingRequest(uint64_t requestId) { return std::unique_ptr(dynamic_cast(getPendingRequestImpl(requestId).release())); } template ReqType *findPendingRequest(uint64_t requestId) { return dynamic_cast(findPendingRequestImpl(requestId)); } const ContactRequest * findContactRequest(UserId userId); void addTempFileUpload(int64_t messageId, const std::string &path); std::string extractTempFileUpload(int64_t messageId); DownloadRequest * findDownloadRequest(int32_t fileId); void extractFileTransferRequests(std::vector &transfers); void addFileTransfer(int32_t fileId, PurpleXfer *xfer, ChatId chatId); bool getFileTransfer(int32_t fileId, PurpleXfer *&xfer, ChatId &chatId); bool getFileIdForTransfer(PurpleXfer *xfer, int &fileId); void removeFileTransfer(int32_t fileId); void removeAllFileTransfers(std::vector &transfers); void addSecretChat(td::td_api::object_ptr secretChat); const td::td_api::secretChat *getSecretChat(SecretChatId id); void deleteSecretChat(SecretChatId id); auto getBasicGroupsWithMember(UserId userId) -> std::vector>; bool hasActiveCall(); void setActiveCall(int32_t id); int32_t getActiveCallId() const { return m_callId; } tgvoip::VoIPController *getCallData(); void removeActiveCall(); PendingMessageQueue pendingMessages; void addPendingReadReceipt(ChatId chatId, MessageId messageId); void extractPendingReadReceipts(ChatId chatId, std::vector &receipts); private: TdAccountData(const TdAccountData &other) = delete; TdAccountData &operator=(const TdAccountData &other) = delete; struct UserInfo { TdUserPtr user; std::string displayName; }; struct ChatInfo { int32_t purpleId; TdChatPtr chat; ChatInfo() : purpleId(0), chat() {} }; struct GroupInfo { TdGroupPtr group; TdGroupInfoPtr fullInfo; bool fullInfoRequested = false; }; struct SupergroupInfo { TdSupergroupPtr group; TdSupergroupInfoPtr fullInfo; TdChatMembersPtr members; bool fullInfoRequested = false; }; struct SendMessageInfo { int64_t messageId; std::string tempFile; }; struct FileTransferInfo { int32_t fileId; ChatId chatId; PurpleXfer *xfer; }; using ChatMap = std::map; using UserMap = std::map; UserMap m_userInfo; ChatMap m_chatInfo; std::map m_groups; std::map m_supergroups; std::map m_secretChats; int m_lastChatPurpleId = 0; // List of contacts for which private chat is not known yet. std::vector m_contactUserIdsNoChat; // Used to remember stuff during asynchronous communication when adding contact std::vector m_addContactRequests; // Chats we want to libpurple-join when we get an updateNewChat about them std::vector m_expectedChats; std::vector> m_requests; // Newly sent messages containing inline images, for which a temporary file must be removed when // transfer is completed std::vector m_sentMessages; // Currently active file transfers for which PurpleXfer is used std::vector m_fileTransfers; // Voice call data std::unique_ptr m_callData; int32_t m_callId; std::unique_ptr getPendingRequestImpl(uint64_t requestId); PendingRequest * findPendingRequestImpl(uint64_t requestId); // Read receipts not sent immediately due to away status (grouped per chat) std::vector> m_pendingReadReceipts; }; #endif