diff options
Diffstat (limited to 'protocols/Teams/src')
27 files changed, 592 insertions, 324 deletions
diff --git a/protocols/Teams/src/main.cpp b/protocols/Teams/src/main.cpp index d2ac241040..07778b2405 100644 --- a/protocols/Teams/src/main.cpp +++ b/protocols/Teams/src/main.cpp @@ -1,3 +1,20 @@ +/* +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" CMPlugin g_plugin; diff --git a/protocols/Teams/src/requests/capabilities.h b/protocols/Teams/src/requests/capabilities.h deleted file mode 100644 index 566d946e3e..0000000000 --- a/protocols/Teams/src/requests/capabilities.h +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright (c) 2015-25 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/>. -*/ - -#ifndef _SKYPE_REQUEST_CAPS_H_ -#define _SKYPE_REQUEST_CAPS_H_ - -struct SendCapabilitiesRequest : public AsyncHttpRequest -{ - SendCapabilitiesRequest(const char *hostname, CTeamsProto *ppro) : - AsyncHttpRequest(REQUEST_PUT, HOST_DEFAULT, "/users/ME/endpoints/" + mir_urlEncode(ppro->m_szId) + "/presenceDocs/messagingService", &CTeamsProto::OnCapabilitiesSended) - { - JSONNode privateInfo; privateInfo.set_name("privateInfo"); - privateInfo << CHAR_PARAM("epname", hostname); - - JSONNode publicInfo; publicInfo.set_name("publicInfo"); - publicInfo << CHAR_PARAM("capabilities", "Audio|Video") << INT_PARAM("typ", 125) - << CHAR_PARAM("skypeNameVersion", "Miranda NG Skype") << CHAR_PARAM("nodeInfo", "xx") << CHAR_PARAM("version", g_szMirVer); - - JSONNode node; - node << CHAR_PARAM("id", "messagingService") << CHAR_PARAM("type", "EndpointPresenceDoc") - << CHAR_PARAM("selfLink", "uri") << privateInfo << publicInfo; - - m_szParam = node.write().c_str(); - } -}; - -#endif //_SKYPE_REQUEST_CAPS_H_ diff --git a/protocols/Teams/src/requests/contacts.h b/protocols/Teams/src/requests/contacts.h deleted file mode 100644 index f0614f10b6..0000000000 --- a/protocols/Teams/src/requests/contacts.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright (c) 2015-25 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/>. -*/ - -#ifndef _SKYPE_REQUEST_CONTACTS_H_ -#define _SKYPE_REQUEST_CONTACTS_H_ - -struct GetContactListRequest : public AsyncHttpRequest -{ - GetContactListRequest() : - AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/contacts", &CTeamsProto::LoadContactList) - { - } -}; - -struct GetContactsAuthRequest : public AsyncHttpRequest -{ - GetContactsAuthRequest() : - AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/invites", &CTeamsProto::LoadContactsAuth) - { - } -}; - -struct AddContactRequest : public AsyncHttpRequest -{ - AddContactRequest(const char *who, const char *greeting = "") : - AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS, "/users/SELF/contacts") - { - JSONNode node; - node << CHAR_PARAM("mri", who) << CHAR_PARAM("greeting", greeting); - m_szParam = node.write().c_str(); - } -}; - -struct AuthAcceptRequest : public AsyncHttpRequest -{ - AuthAcceptRequest(const char *who) : - AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS) - { - m_szUrl.AppendFormat("/users/SELF/invites/%s/accept", mir_urlEncode(who).c_str()); - } -}; - -struct AuthDeclineRequest : public AsyncHttpRequest -{ - AuthDeclineRequest(const char *who) : - AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS) - { - m_szUrl.AppendFormat("/users/SELF/invites/%s/decline", mir_urlEncode(who).c_str()); - } -}; - -struct BlockContactRequest : public AsyncHttpRequest -{ - BlockContactRequest(CTeamsProto *ppro, MCONTACT hContact) : - AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS, "/users/SELF/contacts/blocklist/" + ppro->getId(hContact), &CTeamsProto::OnBlockContact) - { - m_szParam = "{\"report_abuse\":\"false\",\"ui_version\":\"skype.com\"}"; - pUserInfo = (void *)hContact; - } -}; - -struct UnblockContactRequest : public AsyncHttpRequest -{ - UnblockContactRequest(CTeamsProto *ppro, MCONTACT hContact) : - AsyncHttpRequest(REQUEST_DELETE, HOST_CONTACTS, 0, &CTeamsProto::OnUnblockContact) - { - m_szUrl.AppendFormat("/users/SELF/contacts/blocklist/%s", ppro->getId(hContact).c_str()); - pUserInfo = (void *)hContact; - - // TODO: user ip address - this << CHAR_PARAM("reporterIp", "123.123.123.123") << CHAR_PARAM("uiVersion", g_szMirVer); - } -}; - -#endif //_SKYPE_REQUEST_CONTACTS_H_
\ No newline at end of file diff --git a/protocols/Teams/src/requests/poll.h b/protocols/Teams/src/requests/poll.h deleted file mode 100644 index b2573a43e2..0000000000 --- a/protocols/Teams/src/requests/poll.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright (c) 2015-25 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/>. -*/ - -#ifndef _SKYPE_POLL_H_ -#define _SKYPE_POLL_H_ - -struct PollRequest : public AsyncHttpRequest -{ - PollRequest(CTeamsProto *ppro) : - AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/users/ME/endpoints/" + mir_urlEncode(ppro->m_szId) + "/subscriptions/0/poll") - { - flags |= NLHRF_PERSISTENT; - timeout = 120000; - - if (ppro->m_iPollingId != -1) - m_szUrl.AppendFormat("?ackId=%d", ppro->m_iPollingId); - - AddHeader("Referer", "https://web.skype.com/main"); - AddHeader("ClientInfo", "os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=swx-skype.com; clientVer=908/1.85.0.29"); - AddHeader("Accept", "application/json"); - AddHeader("Accept-Language", "en, C"); - } -}; -#endif //_SKYPE_POLL_H_
\ No newline at end of file diff --git a/protocols/Teams/src/stdafx.cxx b/protocols/Teams/src/stdafx.cxx index 4a1e1f0e70..b64bcca703 100644 --- a/protocols/Teams/src/stdafx.cxx +++ b/protocols/Teams/src/stdafx.cxx @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) +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 diff --git a/protocols/Teams/src/stdafx.h b/protocols/Teams/src/stdafx.h index de676dace4..7cbbec2676 100644 --- a/protocols/Teams/src/stdafx.h +++ b/protocols/Teams/src/stdafx.h @@ -88,16 +88,11 @@ struct AsyncHttpRequest : public MTHttpRequest<CTeamsProto> #include "teams_proto.h" -#include "requests/capabilities.h" #include "requests/chatrooms.h" -#include "requests/contacts.h" #include "requests/history.h" -#include "requests/poll.h" #include "requests/profile.h" #include "requests/search.h" #include "requests/status.h" #include "requests/subscriptions.h" -#define POLLING_ERRORS_LIMIT 3 - -#endif //_COMMON_H_
\ No newline at end of file +#endif //_COMMON_H_ diff --git a/protocols/Teams/src/teams_avatars.cpp b/protocols/Teams/src/teams_avatars.cpp index 6bbac21d04..533aa62d08 100644 --- a/protocols/Teams/src/teams_avatars.cpp +++ b/protocols/Teams/src/teams_avatars.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -191,7 +191,7 @@ struct SetAvatarRequest : public AsyncHttpRequest void CTeamsProto::OnSentAvatar(MHttpResponse *response, AsyncHttpRequest*) { - SkypeReply root(response); + TeamsReply root(response); if (root.error()) return; } diff --git a/protocols/Teams/src/teams_chatrooms.cpp b/protocols/Teams/src/teams_chatrooms.cpp index 42c5f269ce..e4012e376f 100644 --- a/protocols/Teams/src/teams_chatrooms.cpp +++ b/protocols/Teams/src/teams_chatrooms.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -353,7 +353,7 @@ void CTeamsProto::SendChatMessage(SESSION_INFO *si, const wchar_t *tszMessage) void CTeamsProto::OnGetChatMembers(MHttpResponse *response, AsyncHttpRequest *pRequest) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; @@ -375,7 +375,7 @@ void CTeamsProto::OnGetChatMembers(MHttpResponse *response, AsyncHttpRequest *pR void CTeamsProto::OnGetChatInfo(MHttpResponse *response, AsyncHttpRequest*) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; diff --git a/protocols/Teams/src/teams_contacts.cpp b/protocols/Teams/src/teams_contacts.cpp index b8f94be72d..8579aed3c3 100644 --- a/protocols/Teams/src/teams_contacts.cpp +++ b/protocols/Teams/src/teams_contacts.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -100,9 +100,11 @@ MCONTACT CTeamsProto::AddContact(const char *skypeId, const char *nick, bool isT return hContact; } +///////////////////////////////////////////////////////////////////////////////////////// + void CTeamsProto::LoadContactsAuth(MHttpResponse *response, AsyncHttpRequest*) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; @@ -140,7 +142,7 @@ void CTeamsProto::LoadContactsAuth(MHttpResponse *response, AsyncHttpRequest*) void CTeamsProto::LoadContactList(MHttpResponse *response, AsyncHttpRequest*) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; @@ -220,16 +222,14 @@ void CTeamsProto::LoadContactList(MHttpResponse *response, AsyncHttpRequest*) } } - PushRequest(new GetContactsAuthRequest()); + PushRequest(new AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/invites", &CTeamsProto::LoadContactsAuth)); } +///////////////////////////////////////////////////////////////////////////////////////// + INT_PTR CTeamsProto::OnRequestAuth(WPARAM hContact, LPARAM) { - if (hContact == INVALID_CONTACT_ID) - return 1; - - PushRequest(new AddContactRequest(getId(hContact))); - return 0; + return AuthRequest(hContact, 0); } INT_PTR CTeamsProto::OnGrantAuth(WPARAM hContact, LPARAM) @@ -237,7 +237,7 @@ INT_PTR CTeamsProto::OnGrantAuth(WPARAM hContact, LPARAM) if (hContact == INVALID_CONTACT_ID) return 1; - PushRequest(new AuthAcceptRequest(getId(hContact))); + PushRequest(new AsyncHttpRequest(REQUEST_POST, HOST_CONTACTS, "/users/SELF/invites/" + mir_urlEncode(getId(hContact)) + "/accept")); return 0; } @@ -257,15 +257,6 @@ bool CTeamsProto::OnContactDeleted(MCONTACT hContact, uint32_t flags) ///////////////////////////////////////////////////////////////////////////////////////// -INT_PTR CTeamsProto::BlockContact(WPARAM hContact, LPARAM) -{ - if (!IsOnline()) return 1; - - if (IDYES == MessageBox(NULL, TranslateT("Are you sure?"), TranslateT("Warning"), MB_YESNO | MB_ICONQUESTION)) - PushRequest(new BlockContactRequest(this, hContact)); - return 0; -} - void CTeamsProto::OnBlockContact(MHttpResponse *response, AsyncHttpRequest *pRequest) { MCONTACT hContact = (DWORD_PTR)pRequest->pUserInfo; @@ -273,12 +264,21 @@ void CTeamsProto::OnBlockContact(MHttpResponse *response, AsyncHttpRequest *pReq Contact::Hide(hContact); } -INT_PTR CTeamsProto::UnblockContact(WPARAM hContact, LPARAM) +INT_PTR CTeamsProto::BlockContact(WPARAM hContact, LPARAM) { - PushRequest(new UnblockContactRequest(this, hContact)); + if (!IsOnline()) return 1; + + if (IDYES == MessageBox(NULL, TranslateT("Are you sure?"), TranslateT("Warning"), MB_YESNO | MB_ICONQUESTION)) { + auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_CONTACTS, "/users/SELF/contacts/blocklist/" + mir_urlEncode(getId(hContact)), &CTeamsProto::OnBlockContact); + pReq->m_szParam = "{\"report_abuse\":\"false\",\"ui_version\":\"skype.com\"}"; + pReq->pUserInfo = (void *)hContact; + PushRequest(pReq); + } return 0; } +///////////////////////////////////////////////////////////////////////////////////////// + void CTeamsProto::OnUnblockContact(MHttpResponse *response, AsyncHttpRequest *pRequest) { if (response == nullptr) @@ -288,3 +288,14 @@ void CTeamsProto::OnUnblockContact(MHttpResponse *response, AsyncHttpRequest *pR Contact::Hide(hContact, false); delSetting(hContact, "IsBlocked"); } + +INT_PTR CTeamsProto::UnblockContact(WPARAM hContact, LPARAM) +{ + if (!IsOnline()) return 1; + + auto *pReq = new AsyncHttpRequest(REQUEST_DELETE, HOST_CONTACTS, "/users/SELF/contacts/blocklist/" + mir_urlEncode(getId(hContact)), &CTeamsProto::OnUnblockContact); + pReq->pUserInfo = (void *)hContact; + pReq << CHAR_PARAM("reporterIp", "123.123.123.123") << CHAR_PARAM("uiVersion", g_szMirVer); // TODO: user ip address + PushRequest(pReq); + return 0; +} diff --git a/protocols/Teams/src/teams_endpoint.cpp b/protocols/Teams/src/teams_endpoint.cpp index 39b6958abb..733008ee75 100644 --- a/protocols/Teams/src/teams_endpoint.cpp +++ b/protocols/Teams/src/teams_endpoint.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -17,15 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" -void CTeamsProto::ProcessTimer() -{ - if (!IsOnline()) - return; - - PushRequest(new GetContactListRequest()); - SendPresence(); -} - void CTeamsProto::SendCreateEndpoint() { auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/users/ME/endpoints", &CTeamsProto::OnEndpointCreated); @@ -83,19 +74,17 @@ void CTeamsProto::OnEndpointCreated(MHttpResponse *response, AsyncHttpRequest*) if (name == "registrationToken") m_szToken = val.Detach(); else if (name == "endpointId") - m_szId = val.Detach(); + m_szEndpoint = val.Detach(); } } - if (m_szId && m_hPollingThread == nullptr) - ForkThread(&CTeamsProto::PollingThread); - + StartTrouter(); PushRequest(new CreateSubscriptionsRequest()); } void CTeamsProto::OnEndpointDeleted(MHttpResponse *, AsyncHttpRequest *) { - m_szId = nullptr; + m_szEndpoint = nullptr; m_szToken = nullptr; } @@ -111,23 +100,9 @@ void CTeamsProto::OnSubscriptionsCreated(MHttpResponse *response, AsyncHttpReque SendPresence(); } -void CTeamsProto::SendPresence() -{ - ptrA epname; - - if (!m_bUseHostnameAsPlace && m_wstrPlace && *m_wstrPlace) - epname = mir_utf8encodeW(m_wstrPlace); - else { - wchar_t compName[MAX_COMPUTERNAME_LENGTH + 1]; - DWORD size = _countof(compName); - GetComputerName(compName, &size); - epname = mir_utf8encodeW(compName); - } - - PushRequest(new SendCapabilitiesRequest(epname, this)); -} +///////////////////////////////////////////////////////////////////////////////////////// -void CTeamsProto::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest*) +void CTeamsProto::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest *) { if (response == nullptr || response->body.IsEmpty()) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); @@ -147,7 +122,7 @@ void CTeamsProto::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest skypenames.destroy(); ReceiveAvatar(0); - PushRequest(new GetContactListRequest()); + PushRequest(new AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/contacts", &CTeamsProto::LoadContactList)); PushRequest(new SyncConversations()); JSONNode root = JSONNode::parse(response->body); @@ -157,6 +132,38 @@ void CTeamsProto::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest PushRequest(new GetProfileRequest(this, 0)); } +void CTeamsProto::SendPresence() +{ + ptrA epname; + + if (!m_bUseHostnameAsPlace && m_wstrPlace && *m_wstrPlace) + epname = mir_utf8encodeW(m_wstrPlace); + else { + wchar_t compName[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD size = _countof(compName); + GetComputerName(compName, &size); + epname = mir_utf8encodeW(compName); + } + + JSONNode privateInfo; privateInfo.set_name("privateInfo"); + privateInfo << CHAR_PARAM("epname", epname); + + JSONNode publicInfo; publicInfo.set_name("publicInfo"); + publicInfo << CHAR_PARAM("capabilities", "Audio|Video") << INT_PARAM("typ", 125) + << CHAR_PARAM("skypeNameVersion", "Miranda NG Skype") << CHAR_PARAM("nodeInfo", "xx") << CHAR_PARAM("version", g_szMirVer); + + JSONNode node; + node << CHAR_PARAM("id", "messagingService") << CHAR_PARAM("type", "EndpointPresenceDoc") + << CHAR_PARAM("selfLink", "uri") << privateInfo << publicInfo; + + auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_DEFAULT, "/users/ME/endpoints/" + mir_urlEncode(m_szEndpoint) + "/presenceDocs/messagingService", + &CTeamsProto::OnCapabilitiesSended); + pReq->m_szParam = node.write().c_str(); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + void CTeamsProto::OnStatusChanged(MHttpResponse *response, AsyncHttpRequest*) { if (response == nullptr || response->body.IsEmpty()) { diff --git a/protocols/Teams/src/teams_files.cpp b/protocols/Teams/src/teams_files.cpp index a6e7d07087..4efb89a662 100644 --- a/protocols/Teams/src/teams_files.cpp +++ b/protocols/Teams/src/teams_files.cpp @@ -1,3 +1,20 @@ +/* +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" //////////////////////////////////////////////////////////////////////////////////////// @@ -48,7 +65,7 @@ void CTeamsProto::ReceiveFileThread(void *param) nlhr.AddHeader("Cookie", szCookie); NLHR_PTR response(Netlib_HttpTransaction(m_hNetlibUser, &nlhr)); if (response) { - SkypeReply reply(response); + TeamsReply reply(response); if (!reply.error()) { auto &root = reply.data(); if (root["content_state"].as_string() == "ready") diff --git a/protocols/Teams/src/teams_history.cpp b/protocols/Teams/src/teams_history.cpp index eb6a9ca39f..a2502a0eb3 100644 --- a/protocols/Teams/src/teams_history.cpp +++ b/protocols/Teams/src/teams_history.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. void CTeamsProto::OnGetServerHistory(MHttpResponse *response, AsyncHttpRequest *pRequest) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; @@ -96,7 +96,7 @@ INT_PTR CTeamsProto::SvcLoadHistory(WPARAM hContact, LPARAM) void CTeamsProto::OnSyncConversations(MHttpResponse *response, AsyncHttpRequest*) { - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) return; diff --git a/protocols/Teams/src/teams_http.cpp b/protocols/Teams/src/teams_http.cpp index 18e3067119..a24b44bfbc 100644 --- a/protocols/Teams/src/teams_http.cpp +++ b/protocols/Teams/src/teams_http.cpp @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) +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 @@ -23,7 +23,7 @@ AsyncHttpRequest::AsyncHttpRequest(int type, SkypeHost host, LPCSTR url, MTHttpR switch (host) { case HOST_API: m_szUrl = "api.skype.com"; break; case HOST_PEOPLE: m_szUrl = "people.skype.com/v2"; break; - case HOST_CONTACTS: m_szUrl = "edge.skype.com/pcs/contacts/v2"; break; + case HOST_CONTACTS: m_szUrl = "contacts.skype.com/contacts/v2"; break; case HOST_GRAPH: m_szUrl = "skypegraph.skype.com"; break; case HOST_LOGIN: m_szUrl = "login.microsoftonline.com"; break; case HOST_TEAMS: m_szUrl = "teams.live.com"; break; diff --git a/protocols/Teams/src/teams_login.cpp b/protocols/Teams/src/teams_login.cpp index 9e4f2a966a..b3c800d13a 100644 --- a/protocols/Teams/src/teams_login.cpp +++ b/protocols/Teams/src/teams_login.cpp @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) +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 @@ -40,8 +40,6 @@ void CTeamsProto::LoggedIn() m_iStatus = m_iDesiredStatus; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - m_impl.m_heartBeat.StartSafe(600 * 1000); - SendCreateEndpoint(); } diff --git a/protocols/Teams/src/teams_menus.cpp b/protocols/Teams/src/teams_menus.cpp index 9238c33946..8605e30a52 100644 --- a/protocols/Teams/src/teams_menus.cpp +++ b/protocols/Teams/src/teams_menus.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 diff --git a/protocols/Teams/src/teams_mood.cpp b/protocols/Teams/src/teams_mood.cpp index 66a2b15444..fb366ac97a 100644 --- a/protocols/Teams/src/teams_mood.cpp +++ b/protocols/Teams/src/teams_mood.cpp @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) +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 diff --git a/protocols/Teams/src/teams_options.cpp b/protocols/Teams/src/teams_options.cpp index 040583ad85..693af22fb1 100644 --- a/protocols/Teams/src/teams_options.cpp +++ b/protocols/Teams/src/teams_options.cpp @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) +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 diff --git a/protocols/Teams/src/teams_polling.cpp b/protocols/Teams/src/teams_polling.cpp index ca59ac8f6a..11a918034e 100644 --- a/protocols/Teams/src/teams_polling.cpp +++ b/protocols/Teams/src/teams_polling.cpp @@ -17,42 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" -void CTeamsProto::PollingThread(void *) -{ - debugLogA(__FUNCTION__ ": entering"); - - m_iPollingId = -1; - - while (true) { - if (m_isTerminated || m_szId == nullptr) - break; - - std::unique_ptr<PollRequest> request(new PollRequest(this)); - request->nlc = m_hPollingConn; - NLHR_PTR response(DoSend(request.get())); - if (m_isTerminated || m_szId == nullptr) - break; - - if (response == nullptr || response->resultCode != 200) { - m_hPollingConn = nullptr; - continue; - } - - m_hPollingConn = response->nlc; - if (!response->body.IsEmpty()) - ParsePollData(response->body); - } - - if (!m_isTerminated) { - debugLogA(__FUNCTION__ ": unexpected termination; switching protocol to offline"); - SetStatus(ID_STATUS_OFFLINE); - } - - m_hPollingConn = nullptr; - m_hPollingThread = nullptr; - debugLogA(__FUNCTION__ ": leaving"); -} - void CTeamsProto::ParsePollData(const char *szData) { debugLogA(__FUNCTION__); @@ -171,6 +135,3 @@ void CTeamsProto::ProcessUserPresence(const JSONNode &node) } } } - -void CTeamsProto::ProcessConversationUpdate(const JSONNode &) {} -void CTeamsProto::ProcessThreadUpdate(const JSONNode &) {} diff --git a/protocols/Teams/src/teams_popups.cpp b/protocols/Teams/src/teams_popups.cpp index efac8c26f9..59cca9e937 100644 --- a/protocols/Teams/src/teams_popups.cpp +++ b/protocols/Teams/src/teams_popups.cpp @@ -1,3 +1,20 @@ +/* +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" void CTeamsProto::InitPopups() diff --git a/protocols/Teams/src/teams_profile.cpp b/protocols/Teams/src/teams_profile.cpp index a26d88b1fe..29a04937c6 100644 --- a/protocols/Teams/src/teams_profile.cpp +++ b/protocols/Teams/src/teams_profile.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -105,7 +105,7 @@ void CTeamsProto::LoadProfile(MHttpResponse *response, AsyncHttpRequest *pReques { MCONTACT hContact = (DWORD_PTR)pRequest->pUserInfo; - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) { ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, 0); return; diff --git a/protocols/Teams/src/teams_proto.cpp b/protocols/Teams/src/teams_proto.cpp index 28dcf637ad..9999e41c2e 100644 --- a/protocols/Teams/src/teams_proto.cpp +++ b/protocols/Teams/src/teams_proto.cpp @@ -1,3 +1,20 @@ +/* +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) : @@ -22,6 +39,13 @@ CTeamsProto::CTeamsProto(const char *protoName, const wchar_t *userName) : 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); @@ -83,6 +107,7 @@ void CTeamsProto::OnModulesLoaded() void CTeamsProto::OnShutdown() { StopQueue(); + StopTrouter(); } INT_PTR CTeamsProto::GetCaps(int type, MCONTACT) @@ -142,7 +167,7 @@ int CTeamsProto::Authorize(MEVENT hDbEvent) if (hContact == INVALID_CONTACT_ID) return 1; - PushRequest(new AuthAcceptRequest(getId(hContact))); + PushRequest(new AsyncHttpRequest(REQUEST_POST, HOST_CONTACTS, "/users/SELF/invites/" + mir_urlEncode(getId(hContact)) + "/accept")); return 0; } @@ -152,7 +177,7 @@ int CTeamsProto::AuthDeny(MEVENT hDbEvent, const wchar_t *) if (hContact == INVALID_CONTACT_ID) return 1; - PushRequest(new AuthDeclineRequest(getId(hContact))); + PushRequest(new AsyncHttpRequest(REQUEST_POST, HOST_CONTACTS, "/users/SELF/invites/" + mir_urlEncode(getId(hContact)) + "/decline")); return 0; } @@ -166,7 +191,15 @@ int CTeamsProto::AuthRequest(MCONTACT hContact, const wchar_t *szMessage) if (hContact == INVALID_CONTACT_ID) return 1; - PushRequest(new AddContactRequest(getId(hContact), T2Utf(szMessage))); + 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; } @@ -203,6 +236,7 @@ int CTeamsProto::SetStatus(int 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); diff --git a/protocols/Teams/src/teams_proto.h b/protocols/Teams/src/teams_proto.h index 850aae8808..c6ad876e86 100644 --- a/protocols/Teams/src/teams_proto.h +++ b/protocols/Teams/src/teams_proto.h @@ -1,4 +1,8 @@ #define TEAMS_CLIENT_ID "8ec6bc83-69c8-4392-8f08-b3c986009232" +#define TEAMS_CLIENTINFO_NAME "skypeteams" +#define TEAMS_CLIENTINFO_VERSION "49/24062722442" + +#define TEAMS_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0 Teams/24165.1410.2974.6689/49" #define DBKEY_ID "id" #define DBKEY_GROUP "DefaultGroup" @@ -39,7 +43,7 @@ class CTeamsProto : public PROTO<CTeamsProto> CTimer m_heartBeat, m_loginPoll; void OnHeartBeat(CTimer *) { - m_proto.ProcessTimer(); + m_proto.TRouterSendJson("ping"); } void OnLoginPoll(CTimer *) { @@ -141,12 +145,6 @@ public: void __cdecl SearchBasicThread(void *param); ////////////////////////////////////////////////////////////////////////////////////// - // services - - static INT_PTR __cdecl SvcEventGetIcon(WPARAM, LPARAM); - static INT_PTR __cdecl SvcGetEventText(WPARAM, LPARAM); - - ////////////////////////////////////////////////////////////////////////////////////// // settings CMOption<bool> m_bAutoHistorySync; @@ -164,7 +162,7 @@ public: // other data int m_iPollingId, m_iMessageId = 1; - ptrA m_szToken, m_szId, m_szOwnSkypeId; + ptrA m_szToken, m_szEndpoint, m_szOwnSkypeId; CMStringA m_szSkypename, m_szMyname, m_szSkypeToken; MCONTACT m_hMyContact; @@ -182,12 +180,6 @@ public: void OnEndpointCreated(MHttpResponse *response, AsyncHttpRequest *pRequest); void OnEndpointDeleted(MHttpResponse *response, AsyncHttpRequest *pRequest); - // oauth - void OnOAuthStart(MHttpResponse *response, AsyncHttpRequest *pRequest); - void OnOAuthConfirm(MHttpResponse* response, AsyncHttpRequest* pRequest); - void OnOAuthAuthorize(MHttpResponse* response, AsyncHttpRequest* pRequest); - void OnOAuthEnd(MHttpResponse *response, AsyncHttpRequest *pRequest); - void OnASMObjectCreated(MHttpResponse *response, AsyncHttpRequest *pRequest); void OnASMObjectUploaded(MHttpResponse *response, AsyncHttpRequest *pRequest); @@ -211,6 +203,7 @@ public: void LoadProfile(MHttpResponse *response, AsyncHttpRequest *pRequest); static INT_PTR __cdecl GlobalParseSkypeUriService(WPARAM, LPARAM lParam); + private: bool m_bHistorySynced; @@ -224,9 +217,6 @@ private: mir_cs messageSyncLock; mir_cs m_StatusLock; - HANDLE m_hPollingThread; - HNETLIBCONN m_hPollingConn; - // avatars void SetAvatarUrl(MCONTACT hContact, const CMStringW &tszUrl); bool ReceiveAvatar(MCONTACT hContact); @@ -303,17 +293,7 @@ private: void SetChatStatus(MCONTACT hContact, int iStatus); - // polling - void __cdecl PollingThread(void*); - bool ParseMessage(const JSONNode &node, DB::EventInfo &dbei); - void ParsePollData(const char*); - - void ProcessNewMessage(const JSONNode &node); - void ProcessUserPresence(const JSONNode &node); - void ProcessThreadUpdate(const JSONNode &node); - void ProcessEndpointPresence(const JSONNode &node); - void ProcessConversationUpdate(const JSONNode &node); // utils template <typename T> @@ -346,8 +326,6 @@ private: static LRESULT CALLBACK PopupDlgProcCall(HWND hPopup, UINT uMsg, WPARAM wParam, LPARAM lParam); - void ProcessTimer(); - void SetString(MCONTACT hContact, const char *pszSetting, const JSONNode &node); CMStringW ChangeTopicForm(); @@ -369,6 +347,40 @@ private: auto *proto = CMPlugin::getInstance((MCONTACT)wParam); return proto ? (proto->*Service)(wParam, lParam) : 0; } + + // trouter +public: + void TRouterProcess(const char *str); + +private: + HNETLIBUSER m_hTrouterNetlibUser; + CMStringA m_szTrouterUrl, m_szTrouterSurl; + WebSocket<CTeamsProto> *m_ws; + MHttpHeaders m_connectParams; + int iCommandId; + + void ProcessNewMessage(const JSONNode &node); + void ProcessUserPresence(const JSONNode &node); + void ProcessThreadUpdate(const JSONNode &node); + void ProcessServerMessage(const std::string &szName, const JSONNode &args); + void ProcessEndpointPresence(const JSONNode &node); + void ProcessConversationUpdate(const JSONNode &node); + + void __cdecl GatewayThread(void *); + + void TRouterSendJson(const char *szName, const JSONNode *node = 0); + void TRouterSendJson(const JSONNode &node); + + void TRouterSendAuthentication(); + void TRouterSendActive(bool); + void TRouterRegister(); + void TRouterRegister(const char *pszAppId, const char *pszKey, const char *pszPath); + + void StartTrouter(); + void StopTrouter(); + + void OnTrouterInfo(MHttpResponse *response, AsyncHttpRequest *pRequest); + void OnTrouterSession(MHttpResponse *response, AsyncHttpRequest *pRequest); }; typedef CProtoDlgBase<CTeamsProto> CTeamsDlgBase; diff --git a/protocols/Teams/src/teams_search.cpp b/protocols/Teams/src/teams_search.cpp index a88daaae47..5cceab3c3f 100644 --- a/protocols/Teams/src/teams_search.cpp +++ b/protocols/Teams/src/teams_search.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 @@ -34,7 +34,7 @@ void CTeamsProto::OnSearch(MHttpResponse *response, AsyncHttpRequest*) { debugLogA(__FUNCTION__); - SkypeReply reply(response); + TeamsReply reply(response); if (reply.error()) { ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0); return; diff --git a/protocols/Teams/src/teams_trouter.cpp b/protocols/Teams/src/teams_trouter.cpp new file mode 100644 index 0000000000..e602724c68 --- /dev/null +++ b/protocols/Teams/src/teams_trouter.cpp @@ -0,0 +1,367 @@ +/* +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" + +#define TEAMS_TROUTER_TTL 86400 +#define TEAMS_TROUTER_TCCV "2024.23.01.2" + +void CTeamsProto::OnTrouterSession(MHttpResponse *response, AsyncHttpRequest *pRequest) +{ + if (response->resultCode != 200) { + LoginError(); + return; + } + + int iStart = 0; + CMStringA szId = response->body.Tokenize(":", iStart); + m_szTrouterUrl = pRequest->m_szUrl; + m_szTrouterUrl.Replace("socket.io/1/", "socket.io/1/websocket/" + szId + "/"); + ForkThread(&CTeamsProto::GatewayThread); +} + +void CTeamsProto::OnTrouterInfo(MHttpResponse *response, AsyncHttpRequest *) +{ + TeamsReply reply(response); + if (reply.error()) { + LoginError(); + return; + } + + auto &root = reply.data(); + m_szTrouterSurl = root["surl"].as_mstring(); + CMStringA ccid = root["ccid"].as_mstring(); + CMStringA szUrl = root["socketio"].as_mstring(); + szUrl += "socket.io/1/"; + + auto *pReq = new AsyncHttpRequest(REQUEST_GET, HOST_OTHER, szUrl, &CTeamsProto::OnTrouterSession); + pReq << CHAR_PARAM("v", "v4"); + + m_connectParams.destroy(); + for (auto &it : root["connectparams"]) { + m_connectParams.AddHeader(it.name(), it.as_string().c_str()); + pReq << CHAR_PARAM(it.name(), it.as_string().c_str()); + } + + pReq << CHAR_PARAM("tc", "{\"cv\":\"" TEAMS_TROUTER_TCCV "\",\"ua\":\"TeamsCDL\",\"hr\":\"\",\"v\":\"" TEAMS_CLIENTINFO_VERSION "\"}") + << CHAR_PARAM("con_num", "1234567890123_1") << CHAR_PARAM("epid", m_szEndpoint) << BOOL_PARAM("auth", true) << INT_PARAM("timeout", 40); + if (!ccid.IsEmpty()) + pReq << CHAR_PARAM("ccid", ccid); + PushRequest(pReq); +} + +void CTeamsProto::StartTrouter() +{ + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_OTHER, "https://go.trouter.teams.microsoft.com/v4/a", &CTeamsProto::OnTrouterInfo); + pReq->m_szUrl.AppendFormat("?epid=%s", m_szEndpoint.get()); + pReq->AddHeader("x-skypetoken", m_szSkypeToken); + pReq->flags |= NLHRF_NODUMPHEADERS; + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::StopTrouter() +{ + m_impl.m_heartBeat.StopSafe(); + + if (m_ws) { + m_ws->terminate(); + m_ws = nullptr; + } +} + +void CTeamsProto::GatewayThread(void *) +{ + m_ws = nullptr; + + MHttpHeaders headers; + headers.AddHeader("x-skypetoken", m_szSkypeToken); + headers.AddHeader("User-Agent", TEAMS_USER_AGENT); + + WebSocket<CTeamsProto> ws(this); + NLHR_PTR pReply(ws.connect(m_hTrouterNetlibUser, m_szTrouterUrl, &headers)); + if (pReply) { + if (pReply->resultCode == 101) { + m_ws = &ws; + + iCommandId = 1; + m_impl.m_heartBeat.StartSafe(30000); + + debugLogA("Websocket connection succeeded"); + ws.run(); + } + else debugLogA("websocket connection failed: %d", pReply->resultCode); + } + else debugLogA("websocket connection failed"); + + StopTrouter(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// TRouter send + +void CTeamsProto::TRouterSendJson(const JSONNode &node) +{ + std::string szJson = "5:::" + node.write(); + if (m_ws) { + m_ws->sendText(szJson.c_str()); + } +} + +void CTeamsProto::TRouterSendJson(const char *szName, const JSONNode *node) +{ + JSONNode payload, args(JSON_ARRAY); + payload << CHAR_PARAM("name", szName); + if (node) { + args.set_name("args"); + args << *node; + payload << args; + } + + std::string szJson = payload.write(); + if (m_ws) + m_ws->sendText(szJson.c_str()); +} + +void CTeamsProto::TRouterSendAuthentication() +{ + JSONNode headers, params, payload; + + headers.set_name("headers"); + headers << CHAR_PARAM("X-Ms-Test-User", "False") << CHAR_PARAM("Authorization", "Bearer " + m_szAccessToken) + << CHAR_PARAM("X-MS-Migration", "True"); + + params.set_name("connectparams"); + for (auto &it : m_connectParams) + params << CHAR_PARAM(it->szName, it->szValue); + + payload << headers << params; + TRouterSendJson(payload); +} + +static char szSuffix[4] = { 'A', 'g', 'Q', 'w' }; + +void CTeamsProto::TRouterSendActive(bool bActive) +{ + CMStringA cv; + srand(time(0)); + for (int i = 0; i < 21; i++) + cv.AppendChar('a' + rand() % 26); + cv.AppendChar(szSuffix[rand() % 4]); + cv += ".0.1"; + + JSONNode payload; + payload << CHAR_PARAM("state", bActive ? "active" : "inactive") << CHAR_PARAM("cv", cv); + TRouterSendJson("user.activity", &payload); +} + +void CTeamsProto::TRouterRegister() +{ + TRouterRegister("NextGenCalling", "DesktopNgc_2.3:SkypeNgc", m_szTrouterSurl + "NGCallManagerWin"); + TRouterRegister("SkypeSpacesWeb", "SkypeSpacesWeb_2.3", m_szTrouterSurl + "SkypeSpacesWeb"); + TRouterRegister("TeamsCDLWebWorker", "TeamsCDLWebWorker_1.9", m_szTrouterSurl); +} + +void CTeamsProto::TRouterRegister(const char *pszAppId, const char *pszKey, const char *pszPath) +{ + JSONNode descr, reg, obj, trouter(JSON_ARRAY), transports; + descr.set_name("clientDescription"); + descr << CHAR_PARAM("appId", pszAppId) << CHAR_PARAM("aesKey", "") << CHAR_PARAM("languageId", "en-US") + << CHAR_PARAM("platform", "edge") << CHAR_PARAM("templateKey", pszKey) << CHAR_PARAM("platformUIVersion", TEAMS_CLIENTINFO_VERSION) + << CHAR_PARAM("productContext", "TFL"); + + obj << CHAR_PARAM("context", "") << CHAR_PARAM("path", pszPath) << INT_PARAM("ttl", TEAMS_TROUTER_TTL); + trouter.set_name("TROUTER"); trouter << obj; + transports.set_name("transports"); transports << trouter; + + reg.set_name("registration"); + reg << descr << CHAR_PARAM("registrationId", m_szEndpoint) << CHAR_PARAM("nodeId", "") << transports; + + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_OTHER, "https://edge.skype.com/registrar/prod/v2/registrations"); + pReq->flags |= NLHRF_NODUMPHEADERS; + pReq->AddHeader("Content-Type", "application/json"); + pReq->AddHeader("X-Skypetoken", m_szSkypeToken); + pReq->AddHeader("Authorization", "Bearer " + m_szAccessToken); + pReq->m_szParam = reg.write().c_str(); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// TRouter receive + +void WebSocket<CTeamsProto>::process(const uint8_t *buf, size_t cbLen) +{ + Netlib_Dump(getConn(), buf, cbLen, false, 0); + + CMStringA payload((const char *)buf, (int)cbLen); + p->TRouterProcess(payload); +} + +static const char* skip3colons(const char *str) +{ + int nColons = 3; + for (const char *p = str; *p; p++) { + if (*p == ':') { + if (--nColons == 0) + return p + 1; + } + } + return str; +} + +void CTeamsProto::TRouterProcess(const char *str) +{ + switch (*str) { + case '1': + TRouterSendAuthentication(); + TRouterSendActive(true); + TRouterRegister(); + break; + + case '3': + if (auto packet = JSONNode::parse(skip3colons(str))) { + std::string szBody(packet["body"].as_string()); + auto message = JSONNode::parse(szBody.c_str()); + if (message) { + Netlib_Logf(m_hTrouterNetlibUser, "Got event:\n%s", message.write_formatted().c_str()); + + const JSONNode &resource = message["resource"]; + + std::string resourceType = message["resourceType"].as_string(); + if (resourceType == "NewMessage") + ProcessNewMessage(resource); + else if (resourceType == "UserPresence") + ProcessUserPresence(resource); + else if (resourceType == "EndpointPresence") + ProcessEndpointPresence(resource); + else if (resourceType == "ConversationUpdate") + ProcessConversationUpdate(resource); + else if (resourceType == "ThreadUpdate") + ProcessThreadUpdate(resource); + } + } + break; + + case '5': + if (auto root = JSONNode::parse(skip3colons(str))) { + std::string szName(root["name"].as_string()); + ProcessServerMessage(szName, root["args"]); + } + break; + } +} + +void CTeamsProto::ProcessEndpointPresence(const JSONNode &node) +{ + debugLogA(__FUNCTION__); + std::string selfLink = node["selfLink"].as_string(); + CMStringA skypename(UrlToSkypeId(selfLink.c_str())); + + MCONTACT hContact = FindContact(skypename); + if (hContact == NULL) + return; + + const JSONNode &publicInfo = node["publicInfo"]; + const JSONNode &privateInfo = node["privateInfo"]; + CMStringA MirVer; + if (publicInfo) { + std::string skypeNameVersion = publicInfo["skypeNameVersion"].as_string(); + std::string version = publicInfo["version"].as_string(); + std::string typ = publicInfo["typ"].as_string(); + int iTyp = atoi(typ.c_str()); + switch (iTyp) { + case 0: + case 1: + MirVer.Append("Skype (Web) " + ParseUrl(version.c_str(), "/")); + break; + case 10: + MirVer.Append("Skype (XBOX) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 17: + MirVer.Append("Skype (Android) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 16: + MirVer.Append("Skype (iOS) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 12: + MirVer.Append("Skype (WinRT) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 15: + MirVer.Append("Skype (WP) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 13: + MirVer.Append("Skype (OSX) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 11: + MirVer.Append("Skype (Windows) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 14: + MirVer.Append("Skype (Linux) " + ParseUrl(skypeNameVersion.c_str(), "/")); + break; + case 125: + MirVer.AppendFormat("Miranda NG Skype %s", version.c_str()); + break; + default: + MirVer.Append("Skype (Unknown)"); + } + } + + if (privateInfo != NULL) { + std::string epname = privateInfo["epname"].as_string(); + if (!epname.empty()) + MirVer.AppendFormat(" [%s]", epname.c_str()); + } + + setString(hContact, "MirVer", MirVer); +} + +void CTeamsProto::ProcessUserPresence(const JSONNode &node) +{ + debugLogA(__FUNCTION__); + + std::string selfLink = node["selfLink"].as_string(); + std::string status = node["availability"].as_string(); + CMStringA skypename = UrlToSkypeId(selfLink.c_str()); + + if (!skypename.IsEmpty()) { + if (IsMe(skypename)) { + int iNewStatus = SkypeToMirandaStatus(status.c_str()); + if (iNewStatus == ID_STATUS_OFFLINE) return; + int old_status = m_iStatus; + m_iDesiredStatus = iNewStatus; + m_iStatus = iNewStatus; + if (old_status != iNewStatus) + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, iNewStatus); + } + else { + MCONTACT hContact = FindContact(skypename); + if (hContact != NULL) + SetContactStatus(hContact, SkypeToMirandaStatus(status.c_str())); + } + } +} + +void CTeamsProto::ProcessServerMessage(const std::string &szName, const JSONNode&) +{ + if (szName == "trouter.message_loss") { + TRouterRegister("TeamsCDLWebWorker", "TeamsCDLWebWorker_1.9", m_szTrouterSurl); + } +} + +void CTeamsProto::ProcessConversationUpdate(const JSONNode &) {} +void CTeamsProto::ProcessThreadUpdate(const JSONNode &) {} diff --git a/protocols/Teams/src/teams_utils.cpp b/protocols/Teams/src/teams_utils.cpp index 59beeaa894..3d23351468 100644 --- a/protocols/Teams/src/teams_utils.cpp +++ b/protocols/Teams/src/teams_utils.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-25 Miranda NG team (https://miranda-ng.org) +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 diff --git a/protocols/Teams/src/teams_utils.h b/protocols/Teams/src/teams_utils.h index f7d205da61..58255e53c1 100644 --- a/protocols/Teams/src/teams_utils.h +++ b/protocols/Teams/src/teams_utils.h @@ -60,9 +60,9 @@ struct CFileUploadParam : public MZeroedObject } }; -struct SkypeReply : public JsonReply +struct TeamsReply : public JsonReply { - SkypeReply(MHttpResponse *response) : + TeamsReply(MHttpResponse *response) : JsonReply(response) { if (m_root) diff --git a/protocols/Teams/src/version.h b/protocols/Teams/src/version.h index 711a86277e..3b23908bca 100644 --- a/protocols/Teams/src/version.h +++ b/protocols/Teams/src/version.h @@ -7,7 +7,7 @@ #define __PLUGIN_NAME "Teams protocol" #define __FILENAME "Teams.dll" -#define __DESCRIPTION "Teams protocol support for Miranda NG." +#define __DESCRIPTION "Microsoft Teams protocol support for Miranda NG." #define __AUTHOR "Miranda NG team" #define __AUTHORWEB "https://miranda-ng.org/p/Teams" #define __COPYRIGHT "© 2025 Miranda NG team" |
