From de40f3be3f08487937525c2ef096dad665dda61d Mon Sep 17 00:00:00 2001 From: dartraiden Date: Sat, 14 Jan 2023 01:30:59 +0300 Subject: Convert sources to CR+LF --- protocols/Telegram/src/auth.cpp | 252 +++++------ protocols/Telegram/src/avatars.cpp | 190 ++++---- protocols/Telegram/src/main.cpp | 104 ++--- protocols/Telegram/src/mt_proto.cpp | 396 ++++++++--------- protocols/Telegram/src/mt_proto.h | 418 +++++++++--------- protocols/Telegram/src/options.cpp | 164 +++---- protocols/Telegram/src/resource.h | 50 +-- protocols/Telegram/src/server.cpp | 838 ++++++++++++++++++------------------ protocols/Telegram/src/stdafx.cxx | 36 +- protocols/Telegram/src/stdafx.h | 94 ++-- protocols/Telegram/src/utils.cpp | 252 +++++------ protocols/Telegram/src/utils.h | 6 +- 12 files changed, 1400 insertions(+), 1400 deletions(-) (limited to 'protocols/Telegram/src') diff --git a/protocols/Telegram/src/auth.cpp b/protocols/Telegram/src/auth.cpp index 3778ff1039..043628ab19 100644 --- a/protocols/Telegram/src/auth.cpp +++ b/protocols/Telegram/src/auth.cpp @@ -1,126 +1,126 @@ -/* -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" - -#include "../../../../miranda-private-keys/Telegram/api.h" - -/////////////////////////////////////////////////////////////////////////////// - -INT_PTR CALLBACK CMTProto::EnterPhoneCode(void *param) -{ - auto *ppro = (CMTProto *)param; - - ENTER_STRING es = {}; - es.szModuleName = ppro->m_szModuleName; - es.caption = TranslateT("Enter secret code sent to your phone"); - if (EnterString(&es)) { - ppro->SendQuery(new TD::checkAuthenticationCode(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); - mir_free(es.ptszResult); - } - else ppro->LogOut(); - return 0; -} - -INT_PTR CALLBACK CMTProto::EnterPassword(void *param) -{ - auto *ppro = (CMTProto *)param; - CMStringW wszTitle(TranslateT("Enter password")); - - auto *pAuth = (TD::authorizationStateWaitPassword *)ppro->pAuthState.get(); - if (!pAuth->password_hint_.empty()) - wszTitle.AppendFormat(TranslateT(" (hint: %s)"), Utf2T(pAuth->password_hint_.c_str()).get()); - - ENTER_STRING es = {}; - es.szModuleName = ppro->m_szModuleName; - es.caption = wszTitle; - es.type = ESF_PASSWORD; - if (EnterString(&es)) { - ppro->SendQuery(new TD::checkAuthenticationPassword(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); - mir_free(es.ptszResult); - } - else ppro->LogOut(); - return 0; -} - -void CMTProto::ProcessAuth(TD::updateAuthorizationState *pObj) -{ - pAuthState = std::move(pObj->authorization_state_); - switch (pAuthState->get_id()) { - case TD::authorizationStateWaitTdlibParameters::ID: - { - MFileVersion v; - char text[100]; - Miranda_GetFileVersion(&v); - mir_snprintf(text, "%d.%d.%d.%d", v[0], v[1], v[2], v[3]); - - CMStringW wszPath(GetProtoFolder()); - - auto *request = new TD::setTdlibParameters(); - request->database_directory_ = T2Utf(wszPath).get(); - request->use_message_database_ = false; - request->use_secret_chats_ = true; - request->api_id_ = MIRANDA_API_ID; - request->api_hash_ = MIRANDA_API_HASH; - request->system_language_code_ = "en"; - request->device_model_ = T2Utf(m_wszDeviceName).get(); - request->application_version_ = text; - request->enable_storage_optimizer_ = true; - SendQuery(request, &CMTProto::OnUpdateAuth); - } - break; - - case TD::authorizationStateWaitPhoneNumber::ID: - SendQuery(new TD::setAuthenticationPhoneNumber(_T2A(m_szOwnPhone).get(), nullptr), &CMTProto::OnUpdateAuth); - break; - - case TD::authorizationStateWaitCode::ID: - CallFunctionSync(EnterPhoneCode, this); - break; - - case TD::authorizationStateWaitPassword::ID: - CallFunctionSync(EnterPassword, this); - break; - - case TD::authorizationStateReady::ID: - OnLoggedIn(); - break; - - case TD::authorizationStateClosed::ID: - debugLogA("Connection terminated, exiting"); - LogOut(); - break; - } -} - -void CMTProto::OnUpdateAuth(td::ClientManager::Response &response) -{ - if (response.object->get_id() == TD::error::ID) { - auto *pError = (TD::error *)response.object.get(); - debugLogA("error happened: %s", to_string(*pError).c_str()); - - if (pError->message_ == "PHONE_CODE_EXPIRED") - Popup(0, TranslateT("Phone code expired"), TranslateT("Error")); - else if (pError->message_ == "INVALID_PHONE_CODE") - Popup(0, TranslateT("Invalid phone code"), TranslateT("Error")); - else if (pError->message_ == "PASSWORD_HASH_INVALID") - Popup(0, TranslateT("Invalid password"), TranslateT("Error")); - - pAuthState = std::move(nullptr); - LogOut(); - } -} +/* +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" + +#include "../../../../miranda-private-keys/Telegram/api.h" + +/////////////////////////////////////////////////////////////////////////////// + +INT_PTR CALLBACK CMTProto::EnterPhoneCode(void *param) +{ + auto *ppro = (CMTProto *)param; + + ENTER_STRING es = {}; + es.szModuleName = ppro->m_szModuleName; + es.caption = TranslateT("Enter secret code sent to your phone"); + if (EnterString(&es)) { + ppro->SendQuery(new TD::checkAuthenticationCode(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); + mir_free(es.ptszResult); + } + else ppro->LogOut(); + return 0; +} + +INT_PTR CALLBACK CMTProto::EnterPassword(void *param) +{ + auto *ppro = (CMTProto *)param; + CMStringW wszTitle(TranslateT("Enter password")); + + auto *pAuth = (TD::authorizationStateWaitPassword *)ppro->pAuthState.get(); + if (!pAuth->password_hint_.empty()) + wszTitle.AppendFormat(TranslateT(" (hint: %s)"), Utf2T(pAuth->password_hint_.c_str()).get()); + + ENTER_STRING es = {}; + es.szModuleName = ppro->m_szModuleName; + es.caption = wszTitle; + es.type = ESF_PASSWORD; + if (EnterString(&es)) { + ppro->SendQuery(new TD::checkAuthenticationPassword(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); + mir_free(es.ptszResult); + } + else ppro->LogOut(); + return 0; +} + +void CMTProto::ProcessAuth(TD::updateAuthorizationState *pObj) +{ + pAuthState = std::move(pObj->authorization_state_); + switch (pAuthState->get_id()) { + case TD::authorizationStateWaitTdlibParameters::ID: + { + MFileVersion v; + char text[100]; + Miranda_GetFileVersion(&v); + mir_snprintf(text, "%d.%d.%d.%d", v[0], v[1], v[2], v[3]); + + CMStringW wszPath(GetProtoFolder()); + + auto *request = new TD::setTdlibParameters(); + request->database_directory_ = T2Utf(wszPath).get(); + request->use_message_database_ = false; + request->use_secret_chats_ = true; + request->api_id_ = MIRANDA_API_ID; + request->api_hash_ = MIRANDA_API_HASH; + request->system_language_code_ = "en"; + request->device_model_ = T2Utf(m_wszDeviceName).get(); + request->application_version_ = text; + request->enable_storage_optimizer_ = true; + SendQuery(request, &CMTProto::OnUpdateAuth); + } + break; + + case TD::authorizationStateWaitPhoneNumber::ID: + SendQuery(new TD::setAuthenticationPhoneNumber(_T2A(m_szOwnPhone).get(), nullptr), &CMTProto::OnUpdateAuth); + break; + + case TD::authorizationStateWaitCode::ID: + CallFunctionSync(EnterPhoneCode, this); + break; + + case TD::authorizationStateWaitPassword::ID: + CallFunctionSync(EnterPassword, this); + break; + + case TD::authorizationStateReady::ID: + OnLoggedIn(); + break; + + case TD::authorizationStateClosed::ID: + debugLogA("Connection terminated, exiting"); + LogOut(); + break; + } +} + +void CMTProto::OnUpdateAuth(td::ClientManager::Response &response) +{ + if (response.object->get_id() == TD::error::ID) { + auto *pError = (TD::error *)response.object.get(); + debugLogA("error happened: %s", to_string(*pError).c_str()); + + if (pError->message_ == "PHONE_CODE_EXPIRED") + Popup(0, TranslateT("Phone code expired"), TranslateT("Error")); + else if (pError->message_ == "INVALID_PHONE_CODE") + Popup(0, TranslateT("Invalid phone code"), TranslateT("Error")); + else if (pError->message_ == "PASSWORD_HASH_INVALID") + Popup(0, TranslateT("Invalid password"), TranslateT("Error")); + + pAuthState = std::move(nullptr); + LogOut(); + } +} diff --git a/protocols/Telegram/src/avatars.cpp b/protocols/Telegram/src/avatars.cpp index b21ddf81d0..bd24079f5f 100644 --- a/protocols/Telegram/src/avatars.cpp +++ b/protocols/Telegram/src/avatars.cpp @@ -1,95 +1,95 @@ -/* -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" - -INT_PTR CMTProto::SvcGetAvatarCaps(WPARAM wParam, LPARAM lParam) -{ - switch (wParam) { - case AF_MAXSIZE: - ((POINT *)lParam)->x = 160; - ((POINT *)lParam)->y = 160; - break; - - case AF_MAXFILESIZE: - return 32000; - - case AF_PROPORTION: - return PIP_SQUARE; - - case AF_FORMATSUPPORTED: - case AF_ENABLED: - case AF_DONTNEEDDELAYS: - case AF_FETCHIFPROTONOTVISIBLE: - case AF_FETCHIFCONTACTOFFLINE: - return 1; - } - return 0; -} - -INT_PTR CMTProto::SvcGetAvatarInfo(WPARAM, LPARAM lParam) -{ - auto *pai = (PROTO_AVATAR_INFORMATION *)lParam; - - ptrW wszPath(getWStringA(pai->hContact, DBKEY_AVATAR_PATH)); - if (wszPath == nullptr) - return GAIR_NOAVATAR; - - pai->format = getByte(pai->hContact, DBKEY_AVATAR_TYPE, PA_FORMAT_JPEG); - wcsncpy_s(pai->filename, wszPath, _TRUNCATE); - - if (::_waccess(pai->filename, 0) == 0) - return GAIR_SUCCESS; - - debugLogA("No avatar"); - return GAIR_NOAVATAR; -} - -INT_PTR CMTProto::SvcGetMyAvatar(WPARAM, LPARAM) -{ - return 1; -} - -INT_PTR CMTProto::SvcSetMyAvatar(WPARAM, LPARAM) -{ - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessFile(TD::updateFile *pObj) -{ - if (auto *pFile = pObj->file_.get()) { - if (!pFile->local_->is_downloading_completed_) - return; - - for (auto &it : m_arUsers) { - if (it->szAvatarHash == pFile->remote_->unique_id_.c_str()) { - PROTO_AVATAR_INFORMATION pai; - wcsncpy_s(pai.filename, Utf2T(pFile->local_->path_.c_str()), _TRUNCATE); - pai.hContact = it->hContact; - pai.format = ProtoGetAvatarFileFormat(pai.filename); - - setByte(pai.hContact, DBKEY_AVATAR_TYPE, pai.format); - setWString(pai.hContact, DBKEY_AVATAR_PATH, pai.filename); - - ProtoBroadcastAck(it->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &pai); - break; - } - } - } -} +/* +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" + +INT_PTR CMTProto::SvcGetAvatarCaps(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) { + case AF_MAXSIZE: + ((POINT *)lParam)->x = 160; + ((POINT *)lParam)->y = 160; + break; + + case AF_MAXFILESIZE: + return 32000; + + case AF_PROPORTION: + return PIP_SQUARE; + + case AF_FORMATSUPPORTED: + case AF_ENABLED: + case AF_DONTNEEDDELAYS: + case AF_FETCHIFPROTONOTVISIBLE: + case AF_FETCHIFCONTACTOFFLINE: + return 1; + } + return 0; +} + +INT_PTR CMTProto::SvcGetAvatarInfo(WPARAM, LPARAM lParam) +{ + auto *pai = (PROTO_AVATAR_INFORMATION *)lParam; + + ptrW wszPath(getWStringA(pai->hContact, DBKEY_AVATAR_PATH)); + if (wszPath == nullptr) + return GAIR_NOAVATAR; + + pai->format = getByte(pai->hContact, DBKEY_AVATAR_TYPE, PA_FORMAT_JPEG); + wcsncpy_s(pai->filename, wszPath, _TRUNCATE); + + if (::_waccess(pai->filename, 0) == 0) + return GAIR_SUCCESS; + + debugLogA("No avatar"); + return GAIR_NOAVATAR; +} + +INT_PTR CMTProto::SvcGetMyAvatar(WPARAM, LPARAM) +{ + return 1; +} + +INT_PTR CMTProto::SvcSetMyAvatar(WPARAM, LPARAM) +{ + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessFile(TD::updateFile *pObj) +{ + if (auto *pFile = pObj->file_.get()) { + if (!pFile->local_->is_downloading_completed_) + return; + + for (auto &it : m_arUsers) { + if (it->szAvatarHash == pFile->remote_->unique_id_.c_str()) { + PROTO_AVATAR_INFORMATION pai; + wcsncpy_s(pai.filename, Utf2T(pFile->local_->path_.c_str()), _TRUNCATE); + pai.hContact = it->hContact; + pai.format = ProtoGetAvatarFileFormat(pai.filename); + + setByte(pai.hContact, DBKEY_AVATAR_TYPE, pai.format); + setWString(pai.hContact, DBKEY_AVATAR_PATH, pai.filename); + + ProtoBroadcastAck(it->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &pai); + break; + } + } + } +} diff --git a/protocols/Telegram/src/main.cpp b/protocols/Telegram/src/main.cpp index 6af8517ab7..c00b87536d 100644 --- a/protocols/Telegram/src/main.cpp +++ b/protocols/Telegram/src/main.cpp @@ -1,52 +1,52 @@ -#include "stdafx.h" - -int hLangpack; -CMPlugin g_plugin; - -#pragma comment(lib, "tdactor.lib") -#pragma comment(lib, "tdcore.lib") -#pragma comment(lib, "tddb.lib") -#pragma comment(lib, "tdlib.lib") -#pragma comment(lib, "tdnet.lib") -#pragma comment(lib, "tdutils.lib") - -///////////////////////////////////////////////////////////////////////////////////////// - -PLUGININFOEX pluginInfo = -{ - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - // {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} - {0xae708252, 0xdf8, 0x42ba, {0x9e, 0xf9, 0x9a, 0xcc, 0x3, 0x8e, 0xed, 0xa7}} -}; - -CMPlugin::CMPlugin() : - ACCPROTOPLUGIN("Telegram", pluginInfo) -{ - SetUniqueId(DBKEY_ID); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// - -static IconItem iconList[] = -{ - { LPGEN("Premium user"), "premuim", IDI_PREMIUM }, -}; - -int CMPlugin::Load() -{ - registerIcon("Protocols/Telegram", iconList, "tg"); - - m_hIcon = ExtraIcon_RegisterIcolib("tg_premium", "Telegram Premium User", getIconHandle(IDI_PREMIUM)); - return 0; -} +#include "stdafx.h" + +int hLangpack; +CMPlugin g_plugin; + +#pragma comment(lib, "tdactor.lib") +#pragma comment(lib, "tdcore.lib") +#pragma comment(lib, "tddb.lib") +#pragma comment(lib, "tdlib.lib") +#pragma comment(lib, "tdnet.lib") +#pragma comment(lib, "tdutils.lib") + +///////////////////////////////////////////////////////////////////////////////////////// + +PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} + {0xae708252, 0xdf8, 0x42ba, {0x9e, 0xf9, 0x9a, 0xcc, 0x3, 0x8e, 0xed, 0xa7}} +}; + +CMPlugin::CMPlugin() : + ACCPROTOPLUGIN("Telegram", pluginInfo) +{ + SetUniqueId(DBKEY_ID); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; + +///////////////////////////////////////////////////////////////////////////////////////// + +static IconItem iconList[] = +{ + { LPGEN("Premium user"), "premuim", IDI_PREMIUM }, +}; + +int CMPlugin::Load() +{ + registerIcon("Protocols/Telegram", iconList, "tg"); + + m_hIcon = ExtraIcon_RegisterIcolib("tg_premium", "Telegram Premium User", getIconHandle(IDI_PREMIUM)); + return 0; +} diff --git a/protocols/Telegram/src/mt_proto.cpp b/protocols/Telegram/src/mt_proto.cpp index a58c3ded1d..2728aa94fb 100644 --- a/protocols/Telegram/src/mt_proto.cpp +++ b/protocols/Telegram/src/mt_proto.cpp @@ -1,198 +1,198 @@ -#include "stdafx.h" - -static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) -{ - if (p1->requestId == p2->requestId) - return 0; - - return (p1->requestId < p2->requestId) ? -1 : 1; -} - -static int CompareUsers(const TG_USER *p1, const TG_USER *p2) -{ - if (p1->id == p2->id) - return 0; - - return (p1->id < p2->id) ? -1 : 1; -} - -CMTProto::CMTProto(const char* protoName, const wchar_t* userName) : - PROTO(protoName, userName), - m_impl(*this), - m_arUsers(10, CompareUsers), - m_arRequests(10, CompareRequests), - m_szOwnPhone(this, "Phone"), - m_wszDeviceName(this, "DeviceName", L"Miranda"), - m_wszDefaultGroup(this, "DefaultGroup", L"Telegram"), - m_bUsePopups(this, "UsePopups", true), - m_bHideGroupchats(this, "HideChats", true) -{ - m_iOwnId = _atoi64(getMStringA(DBKEY_ID)); - - CreateProtoService(PS_CREATEACCMGRUI, &CMTProto::SvcCreateAccMgrUI); - CreateProtoService(PS_GETAVATARCAPS, &CMTProto::SvcGetAvatarCaps); - CreateProtoService(PS_GETAVATARINFO, &CMTProto::SvcGetAvatarInfo); - CreateProtoService(PS_GETMYAVATAR, &CMTProto::SvcGetMyAvatar); - CreateProtoService(PS_SETMYAVATAR, &CMTProto::SvcSetMyAvatar); - - HookProtoEvent(ME_OPT_INITIALISE, &CMTProto::OnOptionsInit); - HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CMTProto::OnDbMarkedRead); - - // default contacts group - if (m_wszDefaultGroup == NULL) - m_wszDefaultGroup = mir_wstrdup(L"WhatsApp"); - m_iBaseGroup = Clist_GroupCreate(0, m_wszDefaultGroup); - - // Create standard network connection - NETLIBUSER nlu = {}; - nlu.flags = NUF_UNICODE; - nlu.szSettingsModule = m_szModuleName; - nlu.szDescriptiveName.w = m_tszUserName; - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - // groupchat initialization - GCREGISTER gcr = {}; - gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; - gcr.ptszDispName = m_tszUserName; - gcr.pszModule = m_szModuleName; - Chat_Register(&gcr); - - // HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); - // HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); -} - -CMTProto::~CMTProto() -{ -} - -void CMTProto::OnModulesLoaded() -{ - CMStringA szId(getMStringA(DBKEY_ID)); - if (!szId.IsEmpty()) - m_arUsers.insert(new TG_USER(_atoi64(szId.c_str()), 0)); - - for (auto &cc : AccContacts()) { - bool isGroupChat = isChatRoom(cc); - szId = getMStringA(cc, isGroupChat ? "ChatRoomID" : DBKEY_ID); - if (!szId.IsEmpty()) { - auto *pUser = new TG_USER(_atoi64(szId.c_str()), cc, isGroupChat); - pUser->szAvatarHash = getMStringA(cc, DBKEY_AVATAR_HASH); - m_arUsers.insert(pUser); - } - } -} - -void CMTProto::OnShutdown() -{ - m_bTerminated = true; -} - -void CMTProto::OnErase() -{ - m_bUnregister = true; - ServerThread(0); - - DeleteDirectoryTreeW(GetProtoFolder(), false); -} - -int CMTProto::OnDbMarkedRead(WPARAM hContact, LPARAM hDbEvent) -{ - if (!hContact) - return 0; - - // filter out only events of my protocol - const char *szProto = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(szProto, m_szModuleName)) - return 0; - - ptrA userId(getStringA(hContact, DBKEY_ID)); - if (userId) { - DBEVENTINFO dbei = {}; - db_event_get(hDbEvent, &dbei); - if (dbei.szId) { - mir_cslock lck(m_csMarkRead); - if (m_markContact) { - if (m_markContact != hContact) - SendMarkRead(); - - m_impl.m_markRead.Stop(); - } - - m_markContact = hContact; - m_markIds.push_back(_atoi64(dbei.szId)); - m_impl.m_markRead.Start(500); - } - } - - return 0; -} - -INT_PTR CMTProto::GetCaps(int type, MCONTACT) -{ - switch (type) { - case PFLAGNUM_1: - return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; - case PFLAGNUM_2: - return PF2_ONLINE; - case PFLAGNUM_3: - return 0; - case PFLAGNUM_4: - return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; - case PFLAGNUM_5: - return 0; - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)L"Phone"; - } - return 0; -} - -int CMTProto::SendMsg(MCONTACT hContact, int, const char *pszMessage) -{ - ptrA szId(getStringA(hContact, DBKEY_ID)); - if (szId == nullptr) - return 0; - - return SendTextMessage(_atoi64(szId), pszMessage); -} - -int CMTProto::SetStatus(int iNewStatus) -{ - if (m_iDesiredStatus == iNewStatus) - return 0; - - int oldStatus = m_iStatus; - - // Routing statuses not supported by Telegram - switch (iNewStatus) { - case ID_STATUS_OFFLINE: - m_iDesiredStatus = iNewStatus; - break; - - case ID_STATUS_ONLINE: - case ID_STATUS_FREECHAT: - default: - m_iDesiredStatus = ID_STATUS_ONLINE; - break; - } - - if (m_iDesiredStatus == ID_STATUS_OFFLINE) { - if (isRunning()) - SendQuery(new TD::close()); - - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else if (!isRunning() && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - ForkThread(&CMTProto::ServerThread); - } - else if (isRunning()) { - m_iStatus = m_iDesiredStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - return 0; -} +#include "stdafx.h" + +static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) +{ + if (p1->requestId == p2->requestId) + return 0; + + return (p1->requestId < p2->requestId) ? -1 : 1; +} + +static int CompareUsers(const TG_USER *p1, const TG_USER *p2) +{ + if (p1->id == p2->id) + return 0; + + return (p1->id < p2->id) ? -1 : 1; +} + +CMTProto::CMTProto(const char* protoName, const wchar_t* userName) : + PROTO(protoName, userName), + m_impl(*this), + m_arUsers(10, CompareUsers), + m_arRequests(10, CompareRequests), + m_szOwnPhone(this, "Phone"), + m_wszDeviceName(this, "DeviceName", L"Miranda"), + m_wszDefaultGroup(this, "DefaultGroup", L"Telegram"), + m_bUsePopups(this, "UsePopups", true), + m_bHideGroupchats(this, "HideChats", true) +{ + m_iOwnId = _atoi64(getMStringA(DBKEY_ID)); + + CreateProtoService(PS_CREATEACCMGRUI, &CMTProto::SvcCreateAccMgrUI); + CreateProtoService(PS_GETAVATARCAPS, &CMTProto::SvcGetAvatarCaps); + CreateProtoService(PS_GETAVATARINFO, &CMTProto::SvcGetAvatarInfo); + CreateProtoService(PS_GETMYAVATAR, &CMTProto::SvcGetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &CMTProto::SvcSetMyAvatar); + + HookProtoEvent(ME_OPT_INITIALISE, &CMTProto::OnOptionsInit); + HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CMTProto::OnDbMarkedRead); + + // default contacts group + if (m_wszDefaultGroup == NULL) + m_wszDefaultGroup = mir_wstrdup(L"WhatsApp"); + m_iBaseGroup = Clist_GroupCreate(0, m_wszDefaultGroup); + + // Create standard network connection + NETLIBUSER nlu = {}; + nlu.flags = NUF_UNICODE; + nlu.szSettingsModule = m_szModuleName; + nlu.szDescriptiveName.w = m_tszUserName; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + // groupchat initialization + GCREGISTER gcr = {}; + gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; + gcr.ptszDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + Chat_Register(&gcr); + + // HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); + // HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); +} + +CMTProto::~CMTProto() +{ +} + +void CMTProto::OnModulesLoaded() +{ + CMStringA szId(getMStringA(DBKEY_ID)); + if (!szId.IsEmpty()) + m_arUsers.insert(new TG_USER(_atoi64(szId.c_str()), 0)); + + for (auto &cc : AccContacts()) { + bool isGroupChat = isChatRoom(cc); + szId = getMStringA(cc, isGroupChat ? "ChatRoomID" : DBKEY_ID); + if (!szId.IsEmpty()) { + auto *pUser = new TG_USER(_atoi64(szId.c_str()), cc, isGroupChat); + pUser->szAvatarHash = getMStringA(cc, DBKEY_AVATAR_HASH); + m_arUsers.insert(pUser); + } + } +} + +void CMTProto::OnShutdown() +{ + m_bTerminated = true; +} + +void CMTProto::OnErase() +{ + m_bUnregister = true; + ServerThread(0); + + DeleteDirectoryTreeW(GetProtoFolder(), false); +} + +int CMTProto::OnDbMarkedRead(WPARAM hContact, LPARAM hDbEvent) +{ + if (!hContact) + return 0; + + // filter out only events of my protocol + const char *szProto = Proto_GetBaseAccountName(hContact); + if (mir_strcmp(szProto, m_szModuleName)) + return 0; + + ptrA userId(getStringA(hContact, DBKEY_ID)); + if (userId) { + DBEVENTINFO dbei = {}; + db_event_get(hDbEvent, &dbei); + if (dbei.szId) { + mir_cslock lck(m_csMarkRead); + if (m_markContact) { + if (m_markContact != hContact) + SendMarkRead(); + + m_impl.m_markRead.Stop(); + } + + m_markContact = hContact; + m_markIds.push_back(_atoi64(dbei.szId)); + m_impl.m_markRead.Start(500); + } + } + + return 0; +} + +INT_PTR CMTProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_3: + return 0; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; + case PFLAGNUM_5: + return 0; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)L"Phone"; + } + return 0; +} + +int CMTProto::SendMsg(MCONTACT hContact, int, const char *pszMessage) +{ + ptrA szId(getStringA(hContact, DBKEY_ID)); + if (szId == nullptr) + return 0; + + return SendTextMessage(_atoi64(szId), pszMessage); +} + +int CMTProto::SetStatus(int iNewStatus) +{ + if (m_iDesiredStatus == iNewStatus) + return 0; + + int oldStatus = m_iStatus; + + // Routing statuses not supported by Telegram + switch (iNewStatus) { + case ID_STATUS_OFFLINE: + m_iDesiredStatus = iNewStatus; + break; + + case ID_STATUS_ONLINE: + case ID_STATUS_FREECHAT: + default: + m_iDesiredStatus = ID_STATUS_ONLINE; + break; + } + + if (m_iDesiredStatus == ID_STATUS_OFFLINE) { + if (isRunning()) + SendQuery(new TD::close()); + + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else if (!isRunning() && !IsStatusConnecting(m_iStatus)) { + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + ForkThread(&CMTProto::ServerThread); + } + else if (isRunning()) { + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + return 0; +} diff --git a/protocols/Telegram/src/mt_proto.h b/protocols/Telegram/src/mt_proto.h index 8bc5cc3c66..bf5c9e667b 100644 --- a/protocols/Telegram/src/mt_proto.h +++ b/protocols/Telegram/src/mt_proto.h @@ -1,209 +1,209 @@ -#pragma once - -#define DBKEY_ID "id" - -#define DBKEY_AVATAR_HASH "AvatarHash" -#define DBKEY_AVATAR_PATH "AvatarPath" -#define DBKEY_AVATAR_TYPE "AvatarType" - -class CMTProto; -typedef void (CMTProto:: *TG_QUERY_HANDLER)(td::ClientManager::Response &response); -typedef void (CMTProto:: *TG_QUERY_HANDLER_FULL)(td::ClientManager::Response &response, void *pUserInfo); - -struct TG_REQUEST_BASE -{ - TG_REQUEST_BASE(td::ClientManager::RequestId _1) : - requestId(_1) - {} - - virtual ~TG_REQUEST_BASE() - {} - - td::ClientManager::RequestId requestId; - - virtual void Execute(CMTProto *ppro, td::ClientManager::Response &response) = 0; -}; - -struct TG_REQUEST : public TG_REQUEST_BASE -{ - TG_REQUEST(td::ClientManager::RequestId _1, TG_QUERY_HANDLER _2) : - TG_REQUEST_BASE(_1), - pHandler(_2) - {} - - TG_QUERY_HANDLER pHandler; - - void Execute(CMTProto *ppro, td::ClientManager::Response &response) override - { - (ppro->*pHandler)(response); - } -}; - -struct TG_REQUEST_FULL : public TG_REQUEST_BASE -{ - TG_REQUEST_FULL(td::ClientManager::RequestId _1, TG_QUERY_HANDLER_FULL _2, void *_3) : - TG_REQUEST_BASE(_1), - pHandler(_2), - pUserInfo(_3) - {} - - TG_QUERY_HANDLER_FULL pHandler; - void *pUserInfo; - - void Execute(CMTProto *ppro, td::ClientManager::Response &response) override - { - (ppro->*pHandler)(response, pUserInfo); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct TG_USER -{ - TG_USER(uint64_t _1, MCONTACT _2, bool _3 = false) : - id(_1), - hContact(_2), - isGroupChat(_3) - {} - - uint64_t id; - MCONTACT hContact; - bool isGroupChat; - CMStringA szAvatarHash; - time_t m_timer1 = 0, m_timer2 = 0; -}; - -class CMTProto : public PROTO -{ - class CProtoImpl - { - friend class CMTProto; - CMTProto &m_proto; - - CTimer m_keepAlive, m_markRead; - void OnKeepAlive(CTimer *) - { m_proto.SendKeepAlive(); - } - - void OnMarkRead(CTimer *) - { m_proto.SendMarkRead(); - } - - CProtoImpl(CMTProto &pro) : - m_proto(pro), - m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), - m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)+1) - { - m_markRead.OnEvent = Callback(this, &CProtoImpl::OnMarkRead); - m_keepAlive.OnEvent = Callback(this, &CProtoImpl::OnKeepAlive); - } - } m_impl; - - bool __forceinline isRunning() const - { return m_pClientMmanager != nullptr; - } - - std::unique_ptr m_pClientMmanager; - TD::object_ptr pAuthState; - - mir_cs m_csMarkRead; - MCONTACT m_markContact = 0; - TD::array m_markIds; - - bool m_bAuthorized, m_bTerminated, m_bUnregister = false; - int32_t m_iClientId, m_iMsgId; - uint64_t m_iQueryId; - - OBJLIST m_arRequests; - - static INT_PTR CALLBACK EnterPhoneCode(void *param); - static INT_PTR CALLBACK EnterPassword(void *param); - - CMStringW GetProtoFolder() const - { return CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName); - } - - void OnEndSession(td::ClientManager::Response &response); - void OnSendMessage(td::ClientManager::Response &response, void *pUserInfo); - void OnUpdateAuth(td::ClientManager::Response &response); - - void LogOut(void); - void OnLoggedIn(void); - void ProcessResponse(td::ClientManager::Response); - - void SendKeepAlive(void); - 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); - - void ProcessAuth(TD::updateAuthorizationState *pObj); - void ProcessChat(TD::updateNewChat *pObj); - void ProcessChatPosition(TD::updateChatPosition *pObj); - void ProcessFile(TD::updateFile *pObj); - void ProcessGroups(TD::updateChatFilters *pObj); - void ProcessMarkRead(TD::updateChatReadInbox *pObj); - void ProcessMessage(TD::updateNewMessage *pObj); - void ProcessStatus(TD::updateUserStatus *pObj); - void ProcessUser(TD::updateUser *pObj); - - void UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str); - - // 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); - - // Popups - HANDLE m_hPopupClass; - - void InitPopups(void); - void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); - -public: - ////////////////////////////////////////////////////////////////////////////////////// - // Ctors - - CMTProto(const char *protoName, const wchar_t *userName); - ~CMTProto(); - - ////////////////////////////////////////////////////////////////////////////////////// - // Virtual functions - - INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; - - int SendMsg(MCONTACT hContact, int flags, const char *pszMessage) override; - int SetStatus(int iNewStatus) override; - - void OnModulesLoaded() override; - void OnShutdown() override; - void OnErase() override; - - // Services ////////////////////////////////////////////////////////////////////////// - - INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetAvatarCaps(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetAvatarInfo(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetMyAvatar(WPARAM, LPARAM); - INT_PTR __cdecl SvcSetMyAvatar(WPARAM, LPARAM); - - // Events //////////////////////////////////////////////////////////////////////////// - - int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnDbMarkedRead(WPARAM, LPARAM); - - // Options /////////////////////////////////////////////////////////////////////////// - - CMOption m_szOwnPhone; // our own phone number - CMOption m_wszDefaultGroup; // clist group to store contacts - CMOption m_wszDeviceName; // how do you see this session in Device List - CMOption m_bHideGroupchats; // do not open chat windows on creation - CMOption m_bUsePopups; - - // Processing Threads //////////////////////////////////////////////////////////////// - - void __cdecl ServerThread(void *); -}; +#pragma once + +#define DBKEY_ID "id" + +#define DBKEY_AVATAR_HASH "AvatarHash" +#define DBKEY_AVATAR_PATH "AvatarPath" +#define DBKEY_AVATAR_TYPE "AvatarType" + +class CMTProto; +typedef void (CMTProto:: *TG_QUERY_HANDLER)(td::ClientManager::Response &response); +typedef void (CMTProto:: *TG_QUERY_HANDLER_FULL)(td::ClientManager::Response &response, void *pUserInfo); + +struct TG_REQUEST_BASE +{ + TG_REQUEST_BASE(td::ClientManager::RequestId _1) : + requestId(_1) + {} + + virtual ~TG_REQUEST_BASE() + {} + + td::ClientManager::RequestId requestId; + + virtual void Execute(CMTProto *ppro, td::ClientManager::Response &response) = 0; +}; + +struct TG_REQUEST : public TG_REQUEST_BASE +{ + TG_REQUEST(td::ClientManager::RequestId _1, TG_QUERY_HANDLER _2) : + TG_REQUEST_BASE(_1), + pHandler(_2) + {} + + TG_QUERY_HANDLER pHandler; + + void Execute(CMTProto *ppro, td::ClientManager::Response &response) override + { + (ppro->*pHandler)(response); + } +}; + +struct TG_REQUEST_FULL : public TG_REQUEST_BASE +{ + TG_REQUEST_FULL(td::ClientManager::RequestId _1, TG_QUERY_HANDLER_FULL _2, void *_3) : + TG_REQUEST_BASE(_1), + pHandler(_2), + pUserInfo(_3) + {} + + TG_QUERY_HANDLER_FULL pHandler; + void *pUserInfo; + + void Execute(CMTProto *ppro, td::ClientManager::Response &response) override + { + (ppro->*pHandler)(response, pUserInfo); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct TG_USER +{ + TG_USER(uint64_t _1, MCONTACT _2, bool _3 = false) : + id(_1), + hContact(_2), + isGroupChat(_3) + {} + + uint64_t id; + MCONTACT hContact; + bool isGroupChat; + CMStringA szAvatarHash; + time_t m_timer1 = 0, m_timer2 = 0; +}; + +class CMTProto : public PROTO +{ + class CProtoImpl + { + friend class CMTProto; + CMTProto &m_proto; + + CTimer m_keepAlive, m_markRead; + void OnKeepAlive(CTimer *) + { m_proto.SendKeepAlive(); + } + + void OnMarkRead(CTimer *) + { m_proto.SendMarkRead(); + } + + CProtoImpl(CMTProto &pro) : + m_proto(pro), + m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), + m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)+1) + { + m_markRead.OnEvent = Callback(this, &CProtoImpl::OnMarkRead); + m_keepAlive.OnEvent = Callback(this, &CProtoImpl::OnKeepAlive); + } + } m_impl; + + bool __forceinline isRunning() const + { return m_pClientMmanager != nullptr; + } + + std::unique_ptr m_pClientMmanager; + TD::object_ptr pAuthState; + + mir_cs m_csMarkRead; + MCONTACT m_markContact = 0; + TD::array m_markIds; + + bool m_bAuthorized, m_bTerminated, m_bUnregister = false; + int32_t m_iClientId, m_iMsgId; + uint64_t m_iQueryId; + + OBJLIST m_arRequests; + + static INT_PTR CALLBACK EnterPhoneCode(void *param); + static INT_PTR CALLBACK EnterPassword(void *param); + + CMStringW GetProtoFolder() const + { return CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName); + } + + void OnEndSession(td::ClientManager::Response &response); + void OnSendMessage(td::ClientManager::Response &response, void *pUserInfo); + void OnUpdateAuth(td::ClientManager::Response &response); + + void LogOut(void); + void OnLoggedIn(void); + void ProcessResponse(td::ClientManager::Response); + + void SendKeepAlive(void); + 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); + + void ProcessAuth(TD::updateAuthorizationState *pObj); + void ProcessChat(TD::updateNewChat *pObj); + void ProcessChatPosition(TD::updateChatPosition *pObj); + void ProcessFile(TD::updateFile *pObj); + void ProcessGroups(TD::updateChatFilters *pObj); + void ProcessMarkRead(TD::updateChatReadInbox *pObj); + void ProcessMessage(TD::updateNewMessage *pObj); + void ProcessStatus(TD::updateUserStatus *pObj); + void ProcessUser(TD::updateUser *pObj); + + void UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str); + + // 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); + + // Popups + HANDLE m_hPopupClass; + + void InitPopups(void); + void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); + +public: + ////////////////////////////////////////////////////////////////////////////////////// + // Ctors + + CMTProto(const char *protoName, const wchar_t *userName); + ~CMTProto(); + + ////////////////////////////////////////////////////////////////////////////////////// + // Virtual functions + + INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; + + int SendMsg(MCONTACT hContact, int flags, const char *pszMessage) override; + int SetStatus(int iNewStatus) override; + + void OnModulesLoaded() override; + void OnShutdown() override; + void OnErase() override; + + // Services ////////////////////////////////////////////////////////////////////////// + + INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetAvatarCaps(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetAvatarInfo(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetMyAvatar(WPARAM, LPARAM); + INT_PTR __cdecl SvcSetMyAvatar(WPARAM, LPARAM); + + // Events //////////////////////////////////////////////////////////////////////////// + + int __cdecl OnOptionsInit(WPARAM, LPARAM); + int __cdecl OnDbMarkedRead(WPARAM, LPARAM); + + // Options /////////////////////////////////////////////////////////////////////////// + + CMOption m_szOwnPhone; // our own phone number + CMOption m_wszDefaultGroup; // clist group to store contacts + CMOption m_wszDeviceName; // how do you see this session in Device List + CMOption m_bHideGroupchats; // do not open chat windows on creation + CMOption m_bUsePopups; + + // Processing Threads //////////////////////////////////////////////////////////////// + + void __cdecl ServerThread(void *); +}; diff --git a/protocols/Telegram/src/options.cpp b/protocols/Telegram/src/options.cpp index 9159d7304e..f3c5cbfb37 100644 --- a/protocols/Telegram/src/options.cpp +++ b/protocols/Telegram/src/options.cpp @@ -1,82 +1,82 @@ -/* -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" - -///////////////////////////////////////////////////////////////////////////////////////// - -class COptionsDlg : public CProtoDlgBase -{ - CCtrlCheck chkHideChats, chkUsePopups; - CCtrlEdit edtGroup, edtPhone, edtDeviceName; - ptrW m_wszOldGroup; - -public: - COptionsDlg(CMTProto *ppro, int iDlgID, bool bFullDlg) : - CProtoDlgBase(ppro, iDlgID), - chkUsePopups(this, IDC_POPUPS), - chkHideChats(this, IDC_HIDECHATS), - edtPhone(this, IDC_PHONE), - edtGroup(this, IDC_DEFGROUP), - edtDeviceName(this, IDC_DEVICE_NAME), - m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) - { - CreateLink(edtPhone, ppro->m_szOwnPhone); - CreateLink(edtGroup, ppro->m_wszDefaultGroup); - CreateLink(edtDeviceName, ppro->m_wszDeviceName); - CreateLink(chkHideChats, ppro->m_bHideGroupchats); - - if (bFullDlg) - CreateLink(chkUsePopups, ppro->m_bUsePopups); - } - - bool OnApply() override - { - if (!mir_wstrlen(m_proto->m_szOwnPhone)) { - SetFocus(edtPhone.GetHwnd()); - return false; - } - - if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) - Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); - return true; - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CMTProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) -{ - auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false); - pDlg->SetParent((HWND)hwndParent); - pDlg->Create(); - return (INT_PTR)pDlg->GetHwnd(); -} - -int CMTProto::OnOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szTitle.w = m_tszUserName; - odp.flags = ODPF_UNICODE; - odp.szGroup.w = LPGENW("Network"); - - odp.position = 1; - odp.szTab.w = LPGENW("Account"); - odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/* +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" + +///////////////////////////////////////////////////////////////////////////////////////// + +class COptionsDlg : public CProtoDlgBase +{ + CCtrlCheck chkHideChats, chkUsePopups; + CCtrlEdit edtGroup, edtPhone, edtDeviceName; + ptrW m_wszOldGroup; + +public: + COptionsDlg(CMTProto *ppro, int iDlgID, bool bFullDlg) : + CProtoDlgBase(ppro, iDlgID), + chkUsePopups(this, IDC_POPUPS), + chkHideChats(this, IDC_HIDECHATS), + edtPhone(this, IDC_PHONE), + edtGroup(this, IDC_DEFGROUP), + edtDeviceName(this, IDC_DEVICE_NAME), + m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) + { + CreateLink(edtPhone, ppro->m_szOwnPhone); + CreateLink(edtGroup, ppro->m_wszDefaultGroup); + CreateLink(edtDeviceName, ppro->m_wszDeviceName); + CreateLink(chkHideChats, ppro->m_bHideGroupchats); + + if (bFullDlg) + CreateLink(chkUsePopups, ppro->m_bUsePopups); + } + + bool OnApply() override + { + if (!mir_wstrlen(m_proto->m_szOwnPhone)) { + SetFocus(edtPhone.GetHwnd()); + return false; + } + + if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) + Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); + return true; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CMTProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) +{ + auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false); + pDlg->SetParent((HWND)hwndParent); + pDlg->Create(); + return (INT_PTR)pDlg->GetHwnd(); +} + +int CMTProto::OnOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.szTitle.w = m_tszUserName; + odp.flags = ODPF_UNICODE; + odp.szGroup.w = LPGENW("Network"); + + odp.position = 1; + odp.szTab.w = LPGENW("Account"); + odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true); + g_plugin.addOptions(wParam, &odp); + return 0; +} diff --git a/protocols/Telegram/src/resource.h b/protocols/Telegram/src/resource.h index 87a094b2bc..9ea3641d02 100644 --- a/protocols/Telegram/src/resource.h +++ b/protocols/Telegram/src/resource.h @@ -1,25 +1,25 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by W:\miranda-ng\protocols\Telegram\res\resource.rc -// -#define IDI_TELEGRAM 100 -#define IDD_ACCMGRUI 101 -#define IDD_OPTIONS 102 -#define IDI_PREMIUM 103 -#define IDC_PHONE 1001 -#define IDC_DEFGROUP 1002 -#define IDC_HIDECHATS 1003 -#define IDC_POPUPS 1004 -#define IDC_DEFGROUP2 1004 -#define IDC_DEVICE_NAME 1005 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 106 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1006 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by W:\miranda-ng\protocols\Telegram\res\resource.rc +// +#define IDI_TELEGRAM 100 +#define IDD_ACCMGRUI 101 +#define IDD_OPTIONS 102 +#define IDI_PREMIUM 103 +#define IDC_PHONE 1001 +#define IDC_DEFGROUP 1002 +#define IDC_HIDECHATS 1003 +#define IDC_POPUPS 1004 +#define IDC_DEFGROUP2 1004 +#define IDC_DEVICE_NAME 1005 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Telegram/src/server.cpp b/protocols/Telegram/src/server.cpp index 24ad055fff..5828478802 100644 --- a/protocols/Telegram/src/server.cpp +++ b/protocols/Telegram/src/server.cpp @@ -1,419 +1,419 @@ -/* -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 CMTProto::OnEndSession(td::ClientManager::Response&) -{ - m_bTerminated = true; -} - -void __cdecl CMTProto::ServerThread(void *) -{ - m_bTerminated = m_bAuthorized = false; - - m_pClientMmanager = std::make_unique(); - m_iClientId = m_pClientMmanager->create_client_id(); - - SendQuery(new TD::getOption("version")); - - while (!m_bTerminated) { - ProcessResponse(m_pClientMmanager->receive(1)); - } - - m_pClientMmanager = std::move(nullptr); -} - -void CMTProto::LogOut() -{ - if (m_bTerminated) - return; - - debugLogA("CMTProto::OnLoggedOut"); - m_bTerminated = true; - m_bAuthorized = false; - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - setAllContactStatuses(ID_STATUS_OFFLINE, false); -} - -void CMTProto::OnLoggedIn() -{ - m_bAuthorized = true; - - debugLogA("CMTProto::OnLoggedIn"); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); - m_iStatus = m_iDesiredStatus; - - if (m_bUnregister) { - SendQuery(new TD::terminateSession()); - SendQuery(new TD::logOut(), &CMTProto::OnEndSession); - } - else SendQuery(new TD::getChats(td::tl::unique_ptr(), 1000)); -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::SendKeepAlive() -{ - time_t now = time(0); - - for (auto &it : m_arUsers) { - if (it->m_timer1 && now - it->m_timer1 > 600) { - it->m_timer1 = 0; - it->m_timer2 = now; - setWord(it->hContact, "Status", ID_STATUS_AWAY); - } - else if (it->m_timer2 && now - it->m_timer2 > 600) { - it->m_timer2 = 0; - setWord(it->hContact, "Status", ID_STATUS_OFFLINE); - } - } -} - -void CMTProto::SendMarkRead() -{ - m_impl.m_markRead.Stop(); - - mir_cslock lck(m_csMarkRead); - uint64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); - SendQuery(new TD::viewMessages(userId, 0, std::move(m_markIds), true)); - m_markContact = 0; -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessResponse(td::ClientManager::Response response) -{ - if (!response.object) - return; - - debugLogA("ProcessResponse: id=%d (%s)", int(response.request_id), to_string(response.object).c_str()); - - if (response.request_id) { - TG_REQUEST tmp(response.request_id, 0); - auto *p = m_arRequests.find(&tmp); - if (p) { - p->Execute(this, response); - m_arRequests.remove(p); - } - return; - } - - switch (response.object->get_id()) { - case TD::updateAuthorizationState::ID: - ProcessAuth((TD::updateAuthorizationState *)response.object.get()); - break; - - case TD::updateChatFilters::ID: - ProcessGroups((TD::updateChatFilters *)response.object.get()); - break; - - case TD::updateChatPosition::ID: - ProcessChatPosition((TD::updateChatPosition *)response.object.get()); - break; - - case TD::updateChatReadInbox::ID: - ProcessMarkRead((TD::updateChatReadInbox *)response.object.get()); - break; - - case TD::updateFile::ID: - ProcessFile((TD::updateFile*)response.object.get()); - break; - - case TD::updateNewChat::ID: - ProcessChat((TD::updateNewChat *)response.object.get()); - break; - - case TD::updateNewMessage::ID: - ProcessMessage((TD::updateNewMessage *)response.object.get()); - break; - - case TD::updateUserStatus::ID: - ProcessStatus((TD::updateUserStatus *)response.object.get()); - break; - - case TD::updateUser::ID: - ProcessUser((TD::updateUser *)response.object.get()); - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMTProto::OnSendMessage(td::ClientManager::Response &response, void *pUserInfo) -{ - if (!response.object) - return; - - if (response.object->get_id() != TD::message::ID) { - debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::message::ID); - return; - } - - auto *pMessage = ((TD::message *)response.object.get()); - auto *pUser = FindUser(pMessage->chat_id_); - if (pUser) { - char szMsgId[100]; - _i64toa(pMessage->id_, szMsgId, 10); - ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, pUserInfo, (LPARAM)szMsgId); - } -} - -int CMTProto::SendTextMessage(uint64_t chatId, const char *pszMessage) -{ - int ret = m_iMsgId++; - - auto pContent = TD::make_object(); - pContent->text_ = TD::make_object(); - pContent->text_->text_ = std::move(pszMessage); - - auto *pMessage = new TD::sendMessage(); - pMessage->chat_id_ = chatId; - pMessage->input_message_content_ = std::move(pContent); - SendQuery(pMessage, &CMTProto::OnSendMessage, (void*)ret); - - return ret; -} - -void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler) -{ - int queryId = ++m_iQueryId; - - 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) - m_arRequests.insert(new TG_REQUEST(queryId, pHandler)); -} - -void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo) -{ - int queryId = ++m_iQueryId; - - auto szDescr = to_string(*pFunc); - debugLogA("Sending full query %d:\n%s", queryId, szDescr.c_str()); - - m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); - - if (pHandler) - m_arRequests.insert(new TG_REQUEST_FULL(queryId, pHandler, pUserInfo)); -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessChat(TD::updateNewChat *pObj) -{ - auto &pChat = pObj->chat_; - if (pChat->type_->get_id() != TD::chatTypePrivate::ID) { - debugLogA("Only private chats are currently supported"); - return; - } - - if (auto *pUser = FindUser(pChat->id_)) - if (!pChat->title_.empty()) - setUString(pUser->hContact, "Nick", pChat->title_.c_str()); -} - -void CMTProto::ProcessChatPosition(TD::updateChatPosition *pObj) -{ - if (pObj->position_->get_id() != TD::chatPosition::ID) { - debugLogA("Unsupport position"); - return; - } - - auto *pUser = FindUser(pObj->chat_id_); - if (pUser == nullptr) { - debugLogA("Unknown chat, skipping"); - return; - } - - auto *pPos = (TD::chatPosition *)pObj->position_.get(); - if (pPos->list_) { - 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)) { - CMStringW wszNewGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszGroup.c_str()); - Clist_SetGroup(pUser->hContact, wszNewGroup); - } - } - } -} - -void CMTProto::ProcessGroups(TD::updateChatFilters *pObj) -{ - for (auto &grp : pObj->chat_filters_) { - if (grp->icon_name_ != "Custom") - continue; - - CMStringA szSetting(FORMAT, "ChatFilter%d", grp->id_); - CMStringW wszOldValue(getMStringW(szSetting)); - Utf2T wszNewValue(grp->title_.c_str()); - if (wszOldValue.IsEmpty()) { - Clist_GroupCreate(m_iBaseGroup, wszNewValue); - setWString(szSetting, wszNewValue); - } - else if (wszOldValue != wszNewValue) { - CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t*)m_wszDefaultGroup, wszNewValue); - MGROUP oldGroup = Clist_GroupExists(wszFullGroup); - if (!oldGroup) - Clist_GroupCreate(m_iBaseGroup, wszFullGroup); - else - Clist_GroupRename(oldGroup, wszFullGroup); - setWString(szSetting, wszNewValue); - } - } -} - -void CMTProto::ProcessMarkRead(TD::updateChatReadInbox *pObj) -{ - auto *pUser = FindUser(pObj->chat_id_); - if (pUser == nullptr) { - debugLogA("message from unknown chat/user, ignored"); - return; - } - - char szId[100]; - _i64toa(pObj->last_read_inbox_message_id_, szId, 10); - MEVENT hLastRead = db_event_getById(m_szModuleName, szId); - if (hLastRead == 0) { - debugLogA("unknown event, ignored"); - return; - } - - bool bExit = false; - for (MEVENT hEvent = db_event_firstUnread(pUser->hContact); hEvent; hEvent = db_event_next(pUser->hContact, hEvent)) { - if (bExit) - break; - - bExit = (hEvent == hLastRead); - - DBEVENTINFO dbei = {}; - if (db_event_get(hEvent, &dbei)) - continue; - - if (!dbei.markedRead()) - db_event_markRead(pUser->hContact, hEvent); - } -} - -void CMTProto::ProcessMessage(TD::updateNewMessage *pObj) -{ - auto &pMessage = pObj->message_; - - auto *pUser = FindUser(pMessage->chat_id_); - if (pUser == nullptr) { - debugLogA("message from unknown chat/user, ignored"); - return; - } - - if (pUser->isGroupChat) { - debugLogA("message from group chat, ignored"); - return; - } - - CMStringA szText(getMessageText(pMessage->content_.get())); - if (szText.IsEmpty()) { - debugLogA("this message was not processed, ignored"); - return; - } - - char szId[100]; - _i64toa(pMessage->id_, szId, 10); - - PROTORECVEVENT pre = {}; - pre.szMessage = szText.GetBuffer(); - pre.szMsgId = szId; - pre.timestamp = pMessage->date_; - if (pMessage->sender_id_->get_id() == TD::messageSenderUser::ID) - if (((TD::messageSenderUser *)pMessage->sender_id_.get())->user_id_ == m_iOwnId) - pre.flags |= PREF_SENT; - ProtoChainRecvMsg(pUser->hContact, &pre); -} - -void CMTProto::ProcessStatus(TD::updateUserStatus *pObj) -{ - if (auto *pUser = FindUser(pObj->user_id_)) { - if (pObj->status_->get_id() == TD::userStatusOnline::ID) - setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); - else if (pObj->status_->get_id() == TD::userStatusOffline::ID) { - setWord(pUser->hContact, "Status", ID_STATUS_AWAY); - pUser->m_timer1 = time(0); - } - else debugLogA("!!!!! Unknown status packet, report it to the developers"); - } -} - -void CMTProto::ProcessUser(TD::updateUser *pObj) -{ - auto *pUser = pObj->user_.get(); - - if (pUser->phone_number_ == _T2A(m_szOwnPhone).get()) { - m_iOwnId = pUser->id_; - - if (!FindUser(pUser->id_)) - m_arUsers.insert(new TG_USER(pUser->id_, 0)); - } - - if (!pUser->is_contact_) { - debugLogA("User doesn't belong to your contacts, skipping"); - return; - } - - auto *pu = AddUser(pUser->id_, false); - UpdateString(pu->hContact, "FirstName", pUser->first_name_); - UpdateString(pu->hContact, "LastName", pUser->last_name_); - UpdateString(pu->hContact, "Phone", pUser->phone_number_); - if (pUser->usernames_) - UpdateString(pu->hContact, "Nick", pUser->usernames_->editable_username_); - - if (pUser->is_premium_) - ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, "tg_premium"); - else - ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, nullptr); - - if (auto *pPhoto = pUser->profile_photo_.get()) { - if (auto *pSmall = pPhoto->small_.get()) { - auto remoteId = pSmall->remote_->unique_id_; - auto storedId = getMStringA(pu->hContact, DBKEY_AVATAR_HASH); - if (remoteId != storedId.c_str()) { - if (!remoteId.empty()) { - pu->szAvatarHash = remoteId.c_str(); - setString(pu->hContact, DBKEY_AVATAR_HASH, remoteId.c_str()); - SendQuery(new TD::downloadFile(pSmall->id_, 5, 0, 0, false)); - } - else delSetting(pu->hContact, DBKEY_AVATAR_HASH); - } - } - } - - if (pUser->status_) { - if (pUser->status_->get_id() == TD::userStatusOffline::ID) { - auto *pOffline = (TD::userStatusOffline *)pUser->status_.get(); - setDword(pu->hContact, "LastSeen", pOffline->was_online_); - } - } -} +/* +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 CMTProto::OnEndSession(td::ClientManager::Response&) +{ + m_bTerminated = true; +} + +void __cdecl CMTProto::ServerThread(void *) +{ + m_bTerminated = m_bAuthorized = false; + + m_pClientMmanager = std::make_unique(); + m_iClientId = m_pClientMmanager->create_client_id(); + + SendQuery(new TD::getOption("version")); + + while (!m_bTerminated) { + ProcessResponse(m_pClientMmanager->receive(1)); + } + + m_pClientMmanager = std::move(nullptr); +} + +void CMTProto::LogOut() +{ + if (m_bTerminated) + return; + + debugLogA("CMTProto::OnLoggedOut"); + m_bTerminated = true; + m_bAuthorized = false; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void CMTProto::OnLoggedIn() +{ + m_bAuthorized = true; + + debugLogA("CMTProto::OnLoggedIn"); + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); + m_iStatus = m_iDesiredStatus; + + if (m_bUnregister) { + SendQuery(new TD::terminateSession()); + SendQuery(new TD::logOut(), &CMTProto::OnEndSession); + } + else SendQuery(new TD::getChats(td::tl::unique_ptr(), 1000)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::SendKeepAlive() +{ + time_t now = time(0); + + for (auto &it : m_arUsers) { + if (it->m_timer1 && now - it->m_timer1 > 600) { + it->m_timer1 = 0; + it->m_timer2 = now; + setWord(it->hContact, "Status", ID_STATUS_AWAY); + } + else if (it->m_timer2 && now - it->m_timer2 > 600) { + it->m_timer2 = 0; + setWord(it->hContact, "Status", ID_STATUS_OFFLINE); + } + } +} + +void CMTProto::SendMarkRead() +{ + m_impl.m_markRead.Stop(); + + mir_cslock lck(m_csMarkRead); + uint64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); + SendQuery(new TD::viewMessages(userId, 0, std::move(m_markIds), true)); + m_markContact = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessResponse(td::ClientManager::Response response) +{ + if (!response.object) + return; + + debugLogA("ProcessResponse: id=%d (%s)", int(response.request_id), to_string(response.object).c_str()); + + if (response.request_id) { + TG_REQUEST tmp(response.request_id, 0); + auto *p = m_arRequests.find(&tmp); + if (p) { + p->Execute(this, response); + m_arRequests.remove(p); + } + return; + } + + switch (response.object->get_id()) { + case TD::updateAuthorizationState::ID: + ProcessAuth((TD::updateAuthorizationState *)response.object.get()); + break; + + case TD::updateChatFilters::ID: + ProcessGroups((TD::updateChatFilters *)response.object.get()); + break; + + case TD::updateChatPosition::ID: + ProcessChatPosition((TD::updateChatPosition *)response.object.get()); + break; + + case TD::updateChatReadInbox::ID: + ProcessMarkRead((TD::updateChatReadInbox *)response.object.get()); + break; + + case TD::updateFile::ID: + ProcessFile((TD::updateFile*)response.object.get()); + break; + + case TD::updateNewChat::ID: + ProcessChat((TD::updateNewChat *)response.object.get()); + break; + + case TD::updateNewMessage::ID: + ProcessMessage((TD::updateNewMessage *)response.object.get()); + break; + + case TD::updateUserStatus::ID: + ProcessStatus((TD::updateUserStatus *)response.object.get()); + break; + + case TD::updateUser::ID: + ProcessUser((TD::updateUser *)response.object.get()); + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMTProto::OnSendMessage(td::ClientManager::Response &response, void *pUserInfo) +{ + if (!response.object) + return; + + if (response.object->get_id() != TD::message::ID) { + debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::message::ID); + return; + } + + auto *pMessage = ((TD::message *)response.object.get()); + auto *pUser = FindUser(pMessage->chat_id_); + if (pUser) { + char szMsgId[100]; + _i64toa(pMessage->id_, szMsgId, 10); + ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, pUserInfo, (LPARAM)szMsgId); + } +} + +int CMTProto::SendTextMessage(uint64_t chatId, const char *pszMessage) +{ + int ret = m_iMsgId++; + + auto pContent = TD::make_object(); + pContent->text_ = TD::make_object(); + pContent->text_->text_ = std::move(pszMessage); + + auto *pMessage = new TD::sendMessage(); + pMessage->chat_id_ = chatId; + pMessage->input_message_content_ = std::move(pContent); + SendQuery(pMessage, &CMTProto::OnSendMessage, (void*)ret); + + return ret; +} + +void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler) +{ + int queryId = ++m_iQueryId; + + 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) + m_arRequests.insert(new TG_REQUEST(queryId, pHandler)); +} + +void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo) +{ + int queryId = ++m_iQueryId; + + auto szDescr = to_string(*pFunc); + debugLogA("Sending full query %d:\n%s", queryId, szDescr.c_str()); + + m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); + + if (pHandler) + m_arRequests.insert(new TG_REQUEST_FULL(queryId, pHandler, pUserInfo)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessChat(TD::updateNewChat *pObj) +{ + auto &pChat = pObj->chat_; + if (pChat->type_->get_id() != TD::chatTypePrivate::ID) { + debugLogA("Only private chats are currently supported"); + return; + } + + if (auto *pUser = FindUser(pChat->id_)) + if (!pChat->title_.empty()) + setUString(pUser->hContact, "Nick", pChat->title_.c_str()); +} + +void CMTProto::ProcessChatPosition(TD::updateChatPosition *pObj) +{ + if (pObj->position_->get_id() != TD::chatPosition::ID) { + debugLogA("Unsupport position"); + return; + } + + auto *pUser = FindUser(pObj->chat_id_); + if (pUser == nullptr) { + debugLogA("Unknown chat, skipping"); + return; + } + + auto *pPos = (TD::chatPosition *)pObj->position_.get(); + if (pPos->list_) { + 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)) { + CMStringW wszNewGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszGroup.c_str()); + Clist_SetGroup(pUser->hContact, wszNewGroup); + } + } + } +} + +void CMTProto::ProcessGroups(TD::updateChatFilters *pObj) +{ + for (auto &grp : pObj->chat_filters_) { + if (grp->icon_name_ != "Custom") + continue; + + CMStringA szSetting(FORMAT, "ChatFilter%d", grp->id_); + CMStringW wszOldValue(getMStringW(szSetting)); + Utf2T wszNewValue(grp->title_.c_str()); + if (wszOldValue.IsEmpty()) { + Clist_GroupCreate(m_iBaseGroup, wszNewValue); + setWString(szSetting, wszNewValue); + } + else if (wszOldValue != wszNewValue) { + CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t*)m_wszDefaultGroup, wszNewValue); + MGROUP oldGroup = Clist_GroupExists(wszFullGroup); + if (!oldGroup) + Clist_GroupCreate(m_iBaseGroup, wszFullGroup); + else + Clist_GroupRename(oldGroup, wszFullGroup); + setWString(szSetting, wszNewValue); + } + } +} + +void CMTProto::ProcessMarkRead(TD::updateChatReadInbox *pObj) +{ + auto *pUser = FindUser(pObj->chat_id_); + if (pUser == nullptr) { + debugLogA("message from unknown chat/user, ignored"); + return; + } + + char szId[100]; + _i64toa(pObj->last_read_inbox_message_id_, szId, 10); + MEVENT hLastRead = db_event_getById(m_szModuleName, szId); + if (hLastRead == 0) { + debugLogA("unknown event, ignored"); + return; + } + + bool bExit = false; + for (MEVENT hEvent = db_event_firstUnread(pUser->hContact); hEvent; hEvent = db_event_next(pUser->hContact, hEvent)) { + if (bExit) + break; + + bExit = (hEvent == hLastRead); + + DBEVENTINFO dbei = {}; + if (db_event_get(hEvent, &dbei)) + continue; + + if (!dbei.markedRead()) + db_event_markRead(pUser->hContact, hEvent); + } +} + +void CMTProto::ProcessMessage(TD::updateNewMessage *pObj) +{ + auto &pMessage = pObj->message_; + + auto *pUser = FindUser(pMessage->chat_id_); + if (pUser == nullptr) { + debugLogA("message from unknown chat/user, ignored"); + return; + } + + if (pUser->isGroupChat) { + debugLogA("message from group chat, ignored"); + return; + } + + CMStringA szText(getMessageText(pMessage->content_.get())); + if (szText.IsEmpty()) { + debugLogA("this message was not processed, ignored"); + return; + } + + char szId[100]; + _i64toa(pMessage->id_, szId, 10); + + PROTORECVEVENT pre = {}; + pre.szMessage = szText.GetBuffer(); + pre.szMsgId = szId; + pre.timestamp = pMessage->date_; + if (pMessage->sender_id_->get_id() == TD::messageSenderUser::ID) + if (((TD::messageSenderUser *)pMessage->sender_id_.get())->user_id_ == m_iOwnId) + pre.flags |= PREF_SENT; + ProtoChainRecvMsg(pUser->hContact, &pre); +} + +void CMTProto::ProcessStatus(TD::updateUserStatus *pObj) +{ + if (auto *pUser = FindUser(pObj->user_id_)) { + if (pObj->status_->get_id() == TD::userStatusOnline::ID) + setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); + else if (pObj->status_->get_id() == TD::userStatusOffline::ID) { + setWord(pUser->hContact, "Status", ID_STATUS_AWAY); + pUser->m_timer1 = time(0); + } + else debugLogA("!!!!! Unknown status packet, report it to the developers"); + } +} + +void CMTProto::ProcessUser(TD::updateUser *pObj) +{ + auto *pUser = pObj->user_.get(); + + if (pUser->phone_number_ == _T2A(m_szOwnPhone).get()) { + m_iOwnId = pUser->id_; + + if (!FindUser(pUser->id_)) + m_arUsers.insert(new TG_USER(pUser->id_, 0)); + } + + if (!pUser->is_contact_) { + debugLogA("User doesn't belong to your contacts, skipping"); + return; + } + + auto *pu = AddUser(pUser->id_, false); + UpdateString(pu->hContact, "FirstName", pUser->first_name_); + UpdateString(pu->hContact, "LastName", pUser->last_name_); + UpdateString(pu->hContact, "Phone", pUser->phone_number_); + if (pUser->usernames_) + UpdateString(pu->hContact, "Nick", pUser->usernames_->editable_username_); + + if (pUser->is_premium_) + ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, "tg_premium"); + else + ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, nullptr); + + if (auto *pPhoto = pUser->profile_photo_.get()) { + if (auto *pSmall = pPhoto->small_.get()) { + auto remoteId = pSmall->remote_->unique_id_; + auto storedId = getMStringA(pu->hContact, DBKEY_AVATAR_HASH); + if (remoteId != storedId.c_str()) { + if (!remoteId.empty()) { + pu->szAvatarHash = remoteId.c_str(); + setString(pu->hContact, DBKEY_AVATAR_HASH, remoteId.c_str()); + SendQuery(new TD::downloadFile(pSmall->id_, 5, 0, 0, false)); + } + else delSetting(pu->hContact, DBKEY_AVATAR_HASH); + } + } + } + + if (pUser->status_) { + if (pUser->status_->get_id() == TD::userStatusOffline::ID) { + auto *pOffline = (TD::userStatusOffline *)pUser->status_.get(); + setDword(pu->hContact, "LastSeen", pOffline->was_online_); + } + } +} diff --git a/protocols/Telegram/src/stdafx.cxx b/protocols/Telegram/src/stdafx.cxx index b08670e67f..8c570f6949 100644 --- a/protocols/Telegram/src/stdafx.cxx +++ b/protocols/Telegram/src/stdafx.cxx @@ -1,18 +1,18 @@ -/* -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" +/* +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" diff --git a/protocols/Telegram/src/stdafx.h b/protocols/Telegram/src/stdafx.h index cae3629bf9..8e56956840 100644 --- a/protocols/Telegram/src/stdafx.h +++ b/protocols/Telegram/src/stdafx.h @@ -1,48 +1,48 @@ -#ifndef _COMMON_H_ -#define _COMMON_H_ - -#include -#include - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "td/telegram/Client.h" -#include "td/telegram/td_api.h" -#include "td/telegram/td_api.hpp" -namespace TD = td::td_api; - -#define MODULE "Telegram" - -#include "version.h" -#include "resource.h" -#include "mt_proto.h" -#include "utils.h" - -struct CMPlugin : public ACCPROTOPLUGIN -{ - CMPlugin(); - - HANDLE m_hIcon; - - int Load() override; -}; - +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "td/telegram/Client.h" +#include "td/telegram/td_api.h" +#include "td/telegram/td_api.hpp" +namespace TD = td::td_api; + +#define MODULE "Telegram" + +#include "version.h" +#include "resource.h" +#include "mt_proto.h" +#include "utils.h" + +struct CMPlugin : public ACCPROTOPLUGIN +{ + CMPlugin(); + + HANDLE m_hIcon; + + int Load() override; +}; + #endif //_COMMON_H_ \ No newline at end of file diff --git a/protocols/Telegram/src/utils.cpp b/protocols/Telegram/src/utils.cpp index 6113870bd3..df4a0d9118 100644 --- a/protocols/Telegram/src/utils.cpp +++ b/protocols/Telegram/src/utils.cpp @@ -1,126 +1,126 @@ -/* -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 CMTProto::UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str) -{ - if (str.empty()) - delSetting(hContact, pszSetting); - else - setUString(hContact, pszSetting, str.c_str()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Users - -TG_USER* CMTProto::FindUser(uint64_t id) -{ - if (auto *pCache = m_arUsers.find((TG_USER *)&id)) - return pCache; - - return nullptr; -} - -TG_USER* CMTProto::AddUser(uint64_t id, bool bIsChat) -{ - auto *pUser = FindUser(id); - if (pUser != nullptr) - return pUser; - - MCONTACT hContact = db_add_contact(); - Proto_AddToContact(hContact, m_szModuleName); - - char szId[100]; - _i64toa(id, szId, 10); - - if (bIsChat) { - Clist_SetGroup(hContact, TranslateT("Chat rooms")); - setByte(hContact, "ChatRoom", 1); - setString(hContact, "ChatRoomID", szId); - } - else { - setString(hContact, DBKEY_ID, szId); - if (mir_wstrlen(m_wszDefaultGroup)) - Clist_SetGroup(hContact, m_wszDefaultGroup); - } - - pUser = new TG_USER(id, hContact, bIsChat); - m_arUsers.insert(pUser); - return pUser; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popups - -void CMTProto::InitPopups(void) -{ - g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups); - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - wchar_t desc[256]; - mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors")); - - POPUPCLASS ppc = {}; - ppc.flags = PCF_UNICODE; - ppc.pszName = name; - ppc.pszDescription.w = desc; - ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon); - ppc.colorBack = RGB(191, 0, 0); //Red - ppc.colorText = RGB(255, 245, 225); //Yellow - ppc.iSeconds = 60; - m_hPopupClass = Popup_RegisterClass(&ppc); - - IcoLib_ReleaseIcon(ppc.hIcon); -} - -void CMTProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle) -{ - if (!m_bUsePopups) - return; - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - CMStringW wszTitle(szTitle); - if (hContact == 0) { - wszTitle.Insert(0, L": "); - wszTitle.Insert(0, m_tszUserName); - } - - POPUPDATACLASS ppd = {}; - ppd.szTitle.w = wszTitle; - ppd.szText.w = szMsg; - ppd.pszClassName = name; - ppd.hContact = hContact; - Popup_AddClass(&ppd); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringA getMessageText(TD::MessageContent *pBody) -{ - if (pBody->get_id() == TD::messageText::ID) { - auto pText = ((TD::messageText *)pBody)->text_.get(); - if (pText->get_id() == TD::formattedText::ID) - return CMStringA(((TD::formattedText *)pText)->text_.c_str()); - } - - return CMStringA(); -} +/* +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 CMTProto::UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str) +{ + if (str.empty()) + delSetting(hContact, pszSetting); + else + setUString(hContact, pszSetting, str.c_str()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Users + +TG_USER* CMTProto::FindUser(uint64_t id) +{ + if (auto *pCache = m_arUsers.find((TG_USER *)&id)) + return pCache; + + return nullptr; +} + +TG_USER* CMTProto::AddUser(uint64_t id, bool bIsChat) +{ + auto *pUser = FindUser(id); + if (pUser != nullptr) + return pUser; + + MCONTACT hContact = db_add_contact(); + Proto_AddToContact(hContact, m_szModuleName); + + char szId[100]; + _i64toa(id, szId, 10); + + if (bIsChat) { + Clist_SetGroup(hContact, TranslateT("Chat rooms")); + setByte(hContact, "ChatRoom", 1); + setString(hContact, "ChatRoomID", szId); + } + else { + setString(hContact, DBKEY_ID, szId); + if (mir_wstrlen(m_wszDefaultGroup)) + Clist_SetGroup(hContact, m_wszDefaultGroup); + } + + pUser = new TG_USER(id, hContact, bIsChat); + m_arUsers.insert(pUser); + return pUser; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Popups + +void CMTProto::InitPopups(void) +{ + g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups); + + char name[256]; + mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); + + wchar_t desc[256]; + mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors")); + + POPUPCLASS ppc = {}; + ppc.flags = PCF_UNICODE; + ppc.pszName = name; + ppc.pszDescription.w = desc; + ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon); + ppc.colorBack = RGB(191, 0, 0); //Red + ppc.colorText = RGB(255, 245, 225); //Yellow + ppc.iSeconds = 60; + m_hPopupClass = Popup_RegisterClass(&ppc); + + IcoLib_ReleaseIcon(ppc.hIcon); +} + +void CMTProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle) +{ + if (!m_bUsePopups) + return; + + char name[256]; + mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); + + CMStringW wszTitle(szTitle); + if (hContact == 0) { + wszTitle.Insert(0, L": "); + wszTitle.Insert(0, m_tszUserName); + } + + POPUPDATACLASS ppd = {}; + ppd.szTitle.w = wszTitle; + ppd.szText.w = szMsg; + ppd.pszClassName = name; + ppd.hContact = hContact; + Popup_AddClass(&ppd); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CMStringA getMessageText(TD::MessageContent *pBody) +{ + if (pBody->get_id() == TD::messageText::ID) { + auto pText = ((TD::messageText *)pBody)->text_.get(); + if (pText->get_id() == TD::formattedText::ID) + return CMStringA(((TD::formattedText *)pText)->text_.c_str()); + } + + return CMStringA(); +} diff --git a/protocols/Telegram/src/utils.h b/protocols/Telegram/src/utils.h index 2526393a5f..a0811da20e 100644 --- a/protocols/Telegram/src/utils.h +++ b/protocols/Telegram/src/utils.h @@ -1,3 +1,3 @@ -#pragma once - -CMStringA getMessageText(TD::MessageContent *pBody); +#pragma once + +CMStringA getMessageText(TD::MessageContent *pBody); -- cgit v1.2.3