From 55cae2b76c0f4bebd69ca50113fbda8b7fd4b641 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Wed, 1 Feb 2023 16:58:06 +0300 Subject: Telegram: group chats support (initial version) --- protocols/Telegram/Telegram.vcxproj | 1 + protocols/Telegram/Telegram.vcxproj.filters | 3 + protocols/Telegram/src/groupchat.cpp | 125 ++++++++++++++++++++++++++++ protocols/Telegram/src/proto.cpp | 22 ++++- protocols/Telegram/src/proto.h | 45 ++++++++-- protocols/Telegram/src/server.cpp | 124 +++++++++++++++++++++++---- protocols/Telegram/src/utils.cpp | 10 ++- 7 files changed, 302 insertions(+), 28 deletions(-) create mode 100644 protocols/Telegram/src/groupchat.cpp (limited to 'protocols') diff --git a/protocols/Telegram/Telegram.vcxproj b/protocols/Telegram/Telegram.vcxproj index c28cc183f5..5bda58b10d 100644 --- a/protocols/Telegram/Telegram.vcxproj +++ b/protocols/Telegram/Telegram.vcxproj @@ -28,6 +28,7 @@ + diff --git a/protocols/Telegram/Telegram.vcxproj.filters b/protocols/Telegram/Telegram.vcxproj.filters index 14ddb0e166..556048fcbc 100644 --- a/protocols/Telegram/Telegram.vcxproj.filters +++ b/protocols/Telegram/Telegram.vcxproj.filters @@ -23,6 +23,9 @@ Source Files + + Source Files + diff --git a/protocols/Telegram/src/groupchat.cpp b/protocols/Telegram/src/groupchat.cpp new file mode 100644 index 0000000000..c0f815aa8a --- /dev/null +++ b/protocols/Telegram/src/groupchat.cpp @@ -0,0 +1,125 @@ +/* +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stdafx.h" + +void CTelegramProto::InitGroupChat(TG_USER *pUser, const TD::chat *pChat, bool bUpdateMembers) +{ + if (pUser->m_si) + return; + + wchar_t wszId[100]; + _i64tow(pUser->id, wszId, 10); + auto *si = pUser->m_si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, Utf2T(pChat->title_.c_str()), pUser); + + if (bUpdateMembers) { + Chat_AddGroup(si, TranslateT("Creator")); + Chat_AddGroup(si, TranslateT("Admin")); + Chat_AddGroup(si, TranslateT("Participant")); + + // push async query to fetch users + SendQuery(new TD::getBasicGroupFullInfo(pUser->id), &CTelegramProto::StartGroupChat, pUser); + } + else { + Chat_AddGroup(si, TranslateT("SuperAdmin")); + Chat_AddGroup(si, TranslateT("Visitor")); + + ptrW wszUserId(getWStringA(DBKEY_ID)), wszNick(Contact::GetInfo(CNF_DISPLAY, 0, m_szModuleName)); + + GCEVENT gce = { si, GC_EVENT_JOIN }; + gce.pszUID.w = wszUserId; + gce.pszNick.w = wszNick; + gce.bIsMe = true; + gce.pszStatus.w = TranslateT("Visitor"); + Chat_Event(&gce); + + gce.bIsMe = false; + gce.pszUID.w = L"---"; + gce.pszNick.w = TranslateT("Admin"); + gce.pszStatus.w = TranslateT("SuperAdmin"); + Chat_Event(&gce); + + Chat_Control(si, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); + Chat_Control(si, SESSION_ONLINE); + } +} + +void CTelegramProto::StartGroupChat(td::ClientManager::Response &response, void *pUserData) +{ + if (!response.object) + return; + + if (response.object->get_id() != TD::basicGroupFullInfo::ID) { + debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::basicGroupFullInfo::ID); + return; + } + + auto *pInfo = ((TD::basicGroupFullInfo *)response.object.get()); + auto *pUser = (TG_USER *)pUserData; + + for (auto &it : pInfo->members_) { + auto *pMember = it.get(); + const wchar_t *pwszRole; + + switch (pMember->status_->get_id()) { + case TD::chatMemberStatusCreator::ID: + pwszRole = TranslateT("Creator"); + break; + case TD::chatMemberStatusAdministrator::ID: + pwszRole = TranslateT("Admin"); + break; + case TD::chatMemberStatusMember::ID: + default: + pwszRole = TranslateT("Participant"); + break; + } + + if (pMember->member_id_->get_id() != TD::messageSenderUser::ID) + continue; + + int64_t memberId = ((TD::messageSenderUser *)pMember->member_id_.get())->user_id_; + auto *pChatUser = FindUser(memberId); + if (pChatUser == nullptr) + continue; + + wchar_t wszUserId[100]; + _i64tow(memberId, wszUserId, 10); + + GCEVENT gce = { pUser->m_si, GC_EVENT_JOIN }; + gce.pszUID.w = wszUserId; + gce.pszStatus.w = pwszRole; + + switch (pChatUser->hContact) { + case 0: + gce.bIsMe = true; + __fallthrough; + + case INVALID_CONTACT_ID: + gce.pszNick.w = pChatUser->wszNick.c_str(); + break; + + default: + gce.pszNick.w = Clist_GetContactDisplayName(pChatUser->hContact); + break; + } + + Chat_Event(&gce); + } + + Chat_Control(pUser->m_si, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); + Chat_Control(pUser->m_si, SESSION_ONLINE); +} diff --git a/protocols/Telegram/src/proto.cpp b/protocols/Telegram/src/proto.cpp index 715e511ea3..2044462846 100644 --- a/protocols/Telegram/src/proto.cpp +++ b/protocols/Telegram/src/proto.cpp @@ -1,5 +1,13 @@ #include "stdafx.h" +int __forceinline CompareId(int64_t id1, int64_t id2) +{ + if (id1 == id2) + return 0; + + return (id1 < id2) ? -1 : 1; +} + static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) { if (p1->requestId == p2->requestId) @@ -9,11 +17,15 @@ static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) } static int CompareUsers(const TG_USER *p1, const TG_USER *p2) -{ - if (p1->id == p2->id) - return 0; +{ return CompareId(p1->id, p2->id); +} + +static int CompareBasicGroups(const TG_BASIC_GROUP *p1, const TG_BASIC_GROUP *p2) +{ return CompareId(p1->id, p2->id); +} - return (p1->id < p2->id) ? -1 : 1; +static int CompareSuperGroups(const TG_SUPER_GROUP *p1, const TG_SUPER_GROUP *p2) +{ return CompareId(p1->id, p2->id); } CTelegramProto::CTelegramProto(const char* protoName, const wchar_t* userName) : @@ -22,6 +34,8 @@ CTelegramProto::CTelegramProto(const char* protoName, const wchar_t* userName) : m_arFiles(1), m_arUsers(10, CompareUsers), m_arRequests(10, CompareRequests), + m_arBasicGroups(10, CompareBasicGroups), + m_arSuperGroups(10, CompareSuperGroups), m_szOwnPhone(this, "Phone"), m_iStatus1(this, "Status1", ID_STATUS_AWAY), m_iStatus2(this, "Status2", ID_STATUS_NA), diff --git a/protocols/Telegram/src/proto.h b/protocols/Telegram/src/proto.h index 0b7fdd5779..d77c5d04fc 100644 --- a/protocols/Telegram/src/proto.h +++ b/protocols/Telegram/src/proto.h @@ -76,17 +76,41 @@ struct TG_FILE_REQUEST struct TG_USER { - TG_USER(uint64_t _1, MCONTACT _2, bool _3 = false) : + TG_USER(int64_t _1, MCONTACT _2, bool _3 = false) : id(_1), hContact(_2), isGroupChat(_3) {} - uint64_t id; + int64_t id; MCONTACT hContact; bool isGroupChat; CMStringA szAvatarHash; + CMStringW wszNick; time_t m_timer1 = 0, m_timer2 = 0; + SESSION_INFO *m_si = nullptr; +}; + +struct TG_SUPER_GROUP +{ + TG_SUPER_GROUP(int64_t _1, TD::object_ptr _2) : + id(_1), + group(std::move(_2)) + {} + + int64_t id; + TD::object_ptr group; +}; + +struct TG_BASIC_GROUP +{ + TG_BASIC_GROUP(int64_t _1, TD::object_ptr _2) : + id(_1), + group(std::move(_2)) + {} + + int64_t id; + TD::object_ptr group; }; class CTelegramProto : public PROTO @@ -128,7 +152,7 @@ class CTelegramProto : public PROTO bool m_bAuthorized, m_bTerminated, m_bUnregister = false, m_bSmileyAdd = false; int32_t m_iClientId, m_iMsgId; - uint64_t m_iQueryId; + int64_t m_iQueryId; OBJLIST m_arRequests; OBJLIST m_arFiles; @@ -154,9 +178,10 @@ class CTelegramProto : public PROTO void SendMarkRead(void); void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler = nullptr); void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo); - int SendTextMessage(uint64_t chatId, const char *pszMessage); + int SendTextMessage(int64_t chatId, const char *pszMessage); void ProcessAuth(TD::updateAuthorizationState *pObj); + void ProcessBasicGroup(TD::updateBasicGroup *pObj); void ProcessChat(TD::updateNewChat *pObj); void ProcessChatPosition(TD::updateChatPosition *pObj); void ProcessFile(TD::updateFile *pObj); @@ -164,6 +189,7 @@ class CTelegramProto : public PROTO void ProcessMarkRead(TD::updateChatReadInbox *pObj); void ProcessMessage(TD::updateNewMessage *pObj); void ProcessStatus(TD::updateUserStatus *pObj); + void ProcessSuperGroup(TD::updateSupergroup *pObj); void ProcessUser(TD::updateUser *pObj); CMStringA GetMessageText(TD::MessageContent *pBody); @@ -178,13 +204,20 @@ class CTelegramProto : public PROTO INT_PTR __cdecl SvcGetMyAvatar(WPARAM, LPARAM); INT_PTR __cdecl SvcSetMyAvatar(WPARAM, LPARAM); + // Group chats + OBJLIST m_arBasicGroups; + OBJLIST m_arSuperGroups; + + void InitGroupChat(TG_USER *pUser, const TD::chat *pChat, bool bUpdateMembers); + void StartGroupChat(td::ClientManager::Response &response, void *pUserData); + // Users int64_t m_iOwnId; MGROUP m_iBaseGroup; OBJLIST m_arUsers; - TG_USER* FindUser(uint64_t id); - TG_USER* AddUser(uint64_t id, bool bIsChat); + TG_USER* FindUser(int64_t id); + TG_USER* AddUser(int64_t id, bool bIsChat); // Popups HANDLE m_hPopupClass; diff --git a/protocols/Telegram/src/server.cpp b/protocols/Telegram/src/server.cpp index b8567a320b..1c30f98fb9 100644 --- a/protocols/Telegram/src/server.cpp +++ b/protocols/Telegram/src/server.cpp @@ -98,7 +98,7 @@ void CTelegramProto::SendMarkRead() m_impl.m_markRead.Stop(); mir_cslock lck(m_csMarkRead); - uint64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); + int64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); SendQuery(new TD::viewMessages(userId, 0, std::move(m_markIds), true)); m_markContact = 0; } @@ -127,6 +127,10 @@ void CTelegramProto::ProcessResponse(td::ClientManager::Response response) ProcessAuth((TD::updateAuthorizationState *)response.object.get()); break; + case TD::updateBasicGroup::ID: + ProcessBasicGroup((TD::updateBasicGroup*)response.object.get()); + break; + case TD::updateChatFilters::ID: ProcessGroups((TD::updateChatFilters *)response.object.get()); break; @@ -140,7 +144,7 @@ void CTelegramProto::ProcessResponse(td::ClientManager::Response response) break; case TD::updateFile::ID: - ProcessFile((TD::updateFile*)response.object.get()); + ProcessFile((TD::updateFile *)response.object.get()); break; case TD::updateNewChat::ID: @@ -151,6 +155,10 @@ void CTelegramProto::ProcessResponse(td::ClientManager::Response response) ProcessMessage((TD::updateNewMessage *)response.object.get()); break; + case TD::updateSupergroup::ID: + ProcessSuperGroup((TD::updateSupergroup *)response.object.get()); + break; + case TD::updateUserStatus::ID: ProcessStatus((TD::updateUserStatus *)response.object.get()); break; @@ -182,7 +190,7 @@ void CTelegramProto::OnSendMessage(td::ClientManager::Response &response, void * } } -int CTelegramProto::SendTextMessage(uint64_t chatId, const char *pszMessage) +int CTelegramProto::SendTextMessage(int64_t chatId, const char *pszMessage) { int ret = m_iMsgId++; @@ -193,7 +201,7 @@ int CTelegramProto::SendTextMessage(uint64_t chatId, const char *pszMessage) auto *pMessage = new TD::sendMessage(); pMessage->chat_id_ = chatId; pMessage->input_message_content_ = std::move(pContent); - SendQuery(pMessage, &CTelegramProto::OnSendMessage, (void*)ret); + SendQuery(pMessage, &CTelegramProto::OnSendMessage, (void *)ret); return ret; } @@ -204,7 +212,7 @@ void CTelegramProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler) auto szDescr = to_string(*pFunc); debugLogA("Sending query %d:\n%s", queryId, szDescr.c_str()); - + m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); if (pHandler) @@ -226,17 +234,60 @@ void CTelegramProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandl /////////////////////////////////////////////////////////////////////////////// +void CTelegramProto::ProcessBasicGroup(TD::updateBasicGroup *pObj) +{ + auto iStatusId = pObj->basic_group_->status_->get_id(); + if (iStatusId == TD::chatMemberStatusBanned::ID) { + debugLogA("We are banned here, skipping"); + return; + } + + TG_BASIC_GROUP tmp(pObj->basic_group_->id_, 0); + auto *pGroup = m_arBasicGroups.find(&tmp); + if (pGroup == nullptr) + m_arBasicGroups.insert(new TG_BASIC_GROUP(tmp.id, std::move(pObj->basic_group_))); + else + pGroup->group = std::move(pObj->basic_group_); + + auto *pUser = AddUser(tmp.id, true); + if (iStatusId == TD::chatMemberStatusLeft::ID) + Contact::Hide(pUser->hContact); +} + void CTelegramProto::ProcessChat(TD::updateNewChat *pObj) { - auto &pChat = pObj->chat_; - if (pChat->type_->get_id() != TD::chatTypePrivate::ID) { - debugLogA("Only private chats are currently supported"); + bool bIsBasicGroup = false; + int64_t chatId; + auto *pChat = pObj->chat_.get(); + + switch(pChat->type_->get_id()) { + case TD::chatTypePrivate::ID: + case TD::chatTypeSecret::ID: + chatId = pChat->id_; + break; + + case TD::chatTypeBasicGroup::ID: + bIsBasicGroup = true; + chatId = ((TD::chatTypeBasicGroup*)pChat->type_.get())->basic_group_id_; + break; + + case TD::chatTypeSupergroup::ID: + chatId = ((TD::chatTypeSupergroup*)pChat->type_.get())->supergroup_id_; + break; + + default: + debugLogA("Invalid chat type %d, ignoring", pChat->type_->get_id()); return; } - if (auto *pUser = FindUser(pChat->id_)) - if (!pChat->title_.empty()) + if (auto *pUser = FindUser(chatId)) { + if (!pChat->title_.empty() && pUser->hContact != INVALID_CONTACT_ID) setUString(pUser->hContact, "Nick", pChat->title_.c_str()); + + if (pUser->isGroupChat) + InitGroupChat(pUser, pChat, bIsBasicGroup); + } + else debugLogA("Unknown chat id %lld, ignoring", chatId); } void CTelegramProto::ProcessChatPosition(TD::updateChatPosition *pObj) @@ -252,19 +303,24 @@ void CTelegramProto::ProcessChatPosition(TD::updateChatPosition *pObj) return; } + if (pUser->hContact == INVALID_CONTACT_ID) + return; + auto *pPos = (TD::chatPosition *)pObj->position_.get(); if (pPos->list_) { - auto *pList = (TD::chatListFilter*)pPos->list_.get(); - + auto *pList = (TD::chatListFilter *)pPos->list_.get(); + CMStringA szSetting(FORMAT, "ChatFilter%d", pList->chat_filter_id_); CMStringW wszGroup(getMStringW(szSetting)); if (!wszGroup.IsEmpty()) { ptrW pwszExistingGroup(Clist_GetGroup(pUser->hContact)); - if (!pwszExistingGroup || !mir_wstrcmp(pwszExistingGroup, m_wszDefaultGroup)) { + if (!pwszExistingGroup + || (!pUser->isGroupChat && !mir_wstrcmp(pwszExistingGroup, m_wszDefaultGroup)) + || (pUser->isGroupChat && !mir_wstrcmp(pwszExistingGroup, ptrW(Chat_GetGroup())))) { CMStringW wszNewGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszGroup.c_str()); Clist_SetGroup(pUser->hContact, wszNewGroup); } - } + } } } @@ -282,7 +338,7 @@ void CTelegramProto::ProcessGroups(TD::updateChatFilters *pObj) setWString(szSetting, wszNewValue); } else if (wszOldValue != wszNewValue) { - CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t*)m_wszDefaultGroup, wszNewValue); + CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszNewValue.get()); MGROUP oldGroup = Clist_GroupExists(wszFullGroup); if (!oldGroup) Clist_GroupCreate(m_iBaseGroup, wszFullGroup); @@ -362,6 +418,9 @@ void CTelegramProto::ProcessMessage(TD::updateNewMessage *pObj) void CTelegramProto::ProcessStatus(TD::updateUserStatus *pObj) { if (auto *pUser = FindUser(pObj->user_id_)) { + if (pUser->hContact == INVALID_CONTACT_ID) + return; + if (pObj->status_->get_id() == TD::userStatusOnline::ID) setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); else if (pObj->status_->get_id() == TD::userStatusOffline::ID) { @@ -372,6 +431,27 @@ void CTelegramProto::ProcessStatus(TD::updateUserStatus *pObj) } } +void CTelegramProto::ProcessSuperGroup(TD::updateSupergroup *pObj) +{ + auto iStatusId = pObj->supergroup_->status_->get_id(); + if (iStatusId == TD::chatMemberStatusBanned::ID) { + debugLogA("We are banned here, skipping"); + return; + } + + TG_SUPER_GROUP tmp(pObj->supergroup_->id_, 0); + + auto *pGroup = m_arSuperGroups.find(&tmp); + if (pGroup == nullptr) + m_arSuperGroups.insert(new TG_SUPER_GROUP(tmp.id, std::move(pObj->supergroup_))); + else + pGroup->group = std::move(pObj->supergroup_); + + auto *pUser = AddUser(tmp.id, true); + if (iStatusId == TD::chatMemberStatusLeft::ID) + Contact::Hide(pUser->hContact); +} + void CTelegramProto::ProcessUser(TD::updateUser *pObj) { auto *pUser = pObj->user_.get(); @@ -384,6 +464,16 @@ void CTelegramProto::ProcessUser(TD::updateUser *pObj) } if (!pUser->is_contact_) { + auto *pu = FindUser(pUser->id_); + if (pu == nullptr) { + pu = new TG_USER(pUser->id_, INVALID_CONTACT_ID, false); + m_arUsers.insert(pu); + } + + pu->wszNick = Utf2T(pUser->first_name_.c_str()); + if (!pUser->last_name_.empty()) + pu->wszNick.AppendFormat(L" %s", Utf2T(pUser->last_name_.c_str()).get()); + debugLogA("User doesn't belong to your contacts, skipping"); return; } @@ -394,6 +484,8 @@ void CTelegramProto::ProcessUser(TD::updateUser *pObj) UpdateString(pu->hContact, "Phone", pUser->phone_number_); if (pUser->usernames_) UpdateString(pu->hContact, "Nick", pUser->usernames_->editable_username_); + if (pu->hContact == 0) + pu->wszNick = Contact::GetInfo(CNF_DISPLAY, 0, m_szModuleName); if (pUser->is_premium_) ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, "tg_premium"); @@ -413,7 +505,7 @@ void CTelegramProto::ProcessUser(TD::updateUser *pObj) else delSetting(pu->hContact, DBKEY_AVATAR_HASH); } } - } + } if (pUser->status_) { if (pUser->status_->get_id() == TD::userStatusOffline::ID) { diff --git a/protocols/Telegram/src/utils.cpp b/protocols/Telegram/src/utils.cpp index bcf222ecab..d921927a60 100644 --- a/protocols/Telegram/src/utils.cpp +++ b/protocols/Telegram/src/utils.cpp @@ -28,15 +28,21 @@ void CTelegramProto::UpdateString(MCONTACT hContact, const char *pszSetting, con ///////////////////////////////////////////////////////////////////////////////////////// // Users -TG_USER* CTelegramProto::FindUser(uint64_t id) +TG_USER* CTelegramProto::FindUser(int64_t id) { if (auto *pCache = m_arUsers.find((TG_USER *)&id)) return pCache; + if (id < 0) { + id = -id; + if (auto *pCache = m_arUsers.find((TG_USER *)&id)) + return pCache; + } + return nullptr; } -TG_USER* CTelegramProto::AddUser(uint64_t id, bool bIsChat) +TG_USER* CTelegramProto::AddUser(int64_t id, bool bIsChat) { auto *pUser = FindUser(id); if (pUser != nullptr) -- cgit v1.2.3