diff options
Diffstat (limited to 'protocols/Teams/src/teams_proto.cpp')
-rw-r--r-- | protocols/Teams/src/teams_proto.cpp | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/protocols/Teams/src/teams_proto.cpp b/protocols/Teams/src/teams_proto.cpp new file mode 100644 index 0000000000..dfa09503e0 --- /dev/null +++ b/protocols/Teams/src/teams_proto.cpp @@ -0,0 +1,297 @@ +/* +Copyright (c) 2025 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 <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +CTeamsProto::CTeamsProto(const char *protoName, const wchar_t *userName) : + PROTO<CTeamsProto>(protoName, userName), + m_impl(*this), + m_requests(10), + m_PopupClasses(1), + m_OutMessages(3, PtrKeySortT), + m_bAutoHistorySync(this, "AutoSync", true), + m_bUseHostnameAsPlace(this, "UseHostName", true), + m_bUseBBCodes(this, "UseBBCodes", true), + m_wstrCListGroup(this, DBKEY_GROUP, L"Teams"), + m_wstrPlace(this, "Place", L""), + m_iMood(this, "Mood", 0), + m_wstrMoodEmoji(this, "MoodEmoji", L""), + m_wstrMoodMessage(this, "XStatusMsg", L"") +{ + // create endpoint + m_szEndpoint = getMStringA("Endpoint"); + if (m_szEndpoint.IsEmpty()) { + m_szEndpoint = Utils_GenerateUUID(); + setString("Endpoint", m_szEndpoint); + } + + // network + NETLIBUSER nlu = {}; + nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szDescriptiveName.w = m_tszUserName; + nlu.szSettingsModule = m_szModuleName; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + CMStringA module(FORMAT, "%s.TRouter", m_szModuleName); + CMStringW descr(FORMAT, TranslateT("%s websocket connection"), m_tszUserName); + nlu.szSettingsModule = module.GetBuffer(); + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_UNICODE; + nlu.szDescriptiveName.w = descr.GetBuffer(); + m_hTrouterNetlibUser = Netlib_RegisterUser(&nlu); + + CreateProtoService(PS_GETAVATARINFO, &CTeamsProto::SvcGetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &CTeamsProto::SvcGetAvatarCaps); + CreateProtoService(PS_GETMYAVATAR, &CTeamsProto::SvcGetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &CTeamsProto::SvcSetMyAvatar); + + CreateProtoService(PS_OFFLINEFILE, &CTeamsProto::SvcOfflineFile); + + CreateProtoService(PS_MENU_REQAUTH, &CTeamsProto::OnRequestAuth); + CreateProtoService(PS_MENU_GRANTAUTH, &CTeamsProto::OnGrantAuth); + + CreateProtoService(PS_MENU_LOADHISTORY, &CTeamsProto::SvcLoadHistory); + CreateProtoService(PS_EMPTY_SRV_HISTORY, &CTeamsProto::SvcEmptyHistory); + + HookProtoEvent(ME_OPT_INITIALISE, &CTeamsProto::OnOptionsInit); + + CreateDirectoryTreeW(GetAvatarPath()); + + // sounds + g_plugin.addSound("skype_inc_call", L"SkypeWeb", LPGENW("Incoming call")); + g_plugin.addSound("skype_call_canceled", L"SkypeWeb", LPGENW("Incoming call canceled")); + + InitGroupChatModule(); +} + +CTeamsProto::~CTeamsProto() +{ + UninitPopups(); +} + +void CTeamsProto::OnEventDeleted(MCONTACT hContact, MEVENT hDbEvent, int flags) +{ + if (!hContact || !(flags & CDF_DEL_HISTORY)) + return; + + DB::EventInfo dbei(hDbEvent, false); + if (dbei.szId) { + auto *pReq = new AsyncHttpRequest(REQUEST_DELETE, HOST_CHATS, "/users/ME/conversations/" + mir_urlEncode(getId(hContact)) + "/messages/" + dbei.szId); + pReq->AddAuthentication(this); + pReq->AddHeader("Origin", "https://web.skype.com"); + pReq->AddHeader("Referer", "https://web.skype.com/"); + PushRequest(pReq); + } +} + +void CTeamsProto::OnEventEdited(MCONTACT hContact, MEVENT, const DBEVENTINFO &dbei) +{ + if (dbei.szId) + SendServerMsg(hContact, dbei.pBlob, _atoi64(dbei.szId)); +} + +void CTeamsProto::OnModulesLoaded() +{ + setAllContactStatuses(ID_STATUS_OFFLINE, false); + + HookProtoEvent(ME_MSG_PRECREATEEVENT, &CTeamsProto::OnPreCreateMessage); + + InitPopups(); +} + +void CTeamsProto::OnShutdown() +{ + StopQueue(); + StopTrouter(); +} + +INT_PTR CTeamsProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_AUTHREQ | PF1_CHAT | PF1_BASICSEARCH | PF1_MODEMSG | PF1_FILE | PF1_SERVERCLIST; + case PFLAGNUM_2: + case PFLAGNUM_3: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND; + case PFLAGNUM_4: + return PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SERVERMSGID | PF4_SERVERFORMATTING; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)TranslateT("Teams ID"); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +MCONTACT CTeamsProto::AddToList(int, PROTOSEARCHRESULT *psr) +{ + debugLogA(__FUNCTION__); + + if (psr->id.a == nullptr) + return NULL; + + MCONTACT hContact; + if (psr->flags & PSR_UNICODE) + hContact = AddContact(T2Utf(psr->id.w), T2Utf(psr->nick.w)); + else + hContact = AddContact(psr->id.a, psr->nick.a); + + return hContact; +} + +MCONTACT CTeamsProto::AddToListByEvent(int, int, MEVENT hDbEvent) +{ + debugLogA(__FUNCTION__); + + DB::EventInfo dbei(hDbEvent); + if (!dbei) + return NULL; + if (mir_strcmp(dbei.szModule, m_szModuleName)) + return NULL; + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) + return NULL; + + DB::AUTH_BLOB blob(dbei.pBlob); + return AddContact(blob.get_email(), blob.get_nick()); +} + +int CTeamsProto::Authorize(MEVENT hDbEvent) +{ + MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); + if (hContact == INVALID_CONTACT_ID) + return 1; + + PushRequest(new AsyncHttpRequest(REQUEST_POST, HOST_CONTACTS, "/users/SELF/invites/" + mir_urlEncode(getId(hContact)) + "/accept")); + return 0; +} + +int CTeamsProto::AuthDeny(MEVENT hDbEvent, const wchar_t *) +{ + MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); + if (hContact == INVALID_CONTACT_ID) + return 1; + + PushRequest(new AsyncHttpRequest(REQUEST_POST, HOST_CONTACTS, "/users/SELF/invites/" + mir_urlEncode(getId(hContact)) + "/decline")); + return 0; +} + +int CTeamsProto::AuthRecv(MCONTACT, DB::EventInfo &dbei) +{ + return Proto_AuthRecv(m_szModuleName, dbei); +} + +int CTeamsProto::AuthRequest(MCONTACT hContact, const wchar_t *szMessage) +{ + if (hContact == INVALID_CONTACT_ID) + return 1; + + auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS, "/users/SELF/contacts"); + + JSONNode node; + node << CHAR_PARAM("mri", getId(hContact)); + if (mir_wstrlen(szMessage)) + node << WCHAR_PARAM("greeting", szMessage); + pReq->m_szParam = node.write().c_str(); + + PushRequest(pReq); + return 0; +} + +int CTeamsProto::GetInfo(MCONTACT hContact, int) +{ + if (isChatRoom(hContact)) + return 1; + + GetProfileInfo(hContact); + return 0; +} + +int CTeamsProto::SendMsg(MCONTACT hContact, MEVENT, const char *szMessage) +{ + return SendServerMsg(hContact, szMessage); +} + +int CTeamsProto::SetStatus(int iNewStatus) +{ + if (iNewStatus == m_iDesiredStatus) + return 0; + + debugLogA(__FUNCTION__ ": changing status from %i to %i", m_iStatus, iNewStatus); + + int old_status = m_iStatus; + m_iDesiredStatus = iNewStatus; + + if (iNewStatus == ID_STATUS_OFFLINE) { + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + StopQueue(); + StopTrouter(); + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, ID_STATUS_OFFLINE); + + if (!Miranda_IsTerminated()) + setAllContactStatuses(ID_STATUS_OFFLINE, false); + return 0; + } + + if (m_iStatus == ID_STATUS_OFFLINE) + Login(); + else + SetServerStatus(m_iDesiredStatus); + + return 0; +} + +int CTeamsProto::UserIsTyping(MCONTACT hContact, int iState) +{ + JSONNode node; + node << INT64_PARAM("clientmessageid", getRandomId()) << CHAR_PARAM("contenttype", "Application/Message") << CHAR_PARAM("content", "") + << CHAR_PARAM("messagetype", (iState == PROTOTYPE_SELFTYPING_ON) ? "Control/Typing" : "Control/ClearTyping"); + + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_CHATS, "/users/ME/conversations/" + mir_urlEncode(getId(hContact)) + "/messages"); + pReq->m_szParam = node.write().c_str(); + PushRequest(pReq); + return 0; +} + +int CTeamsProto::RecvContacts(MCONTACT hContact, DB::EventInfo &dbei) +{ + PROTOSEARCHRESULT **isrList = (PROTOSEARCHRESULT **)dbei.pBlob; + + int nCount = dbei.cbBlob; + + uint32_t cbBlob = 0; + for (int i = 0; i < nCount; i++) + cbBlob += int(/*mir_wstrlen(isrList[i]->nick.w)*/0 + 2 + mir_wstrlen(isrList[i]->id.w)); + + char *pBlob = (char *)mir_calloc(cbBlob); + char *pCurBlob = pBlob; + + for (int i = 0; i < nCount; i++) { + pCurBlob += mir_strlen(pCurBlob) + 1; + + mir_strcpy(pCurBlob, _T2A(isrList[i]->id.w)); + pCurBlob += mir_strlen(pCurBlob) + 1; + } + + dbei.szModule = m_szModuleName; + dbei.eventType = EVENTTYPE_CONTACTS; + dbei.cbBlob = cbBlob; + dbei.pBlob = pBlob; + db_event_add(hContact, &dbei); + + mir_free(pBlob); + return 0; +} |