diff options
author | George Hazan <george.hazan@gmail.com> | 2025-04-18 16:12:38 +0300 |
---|---|---|
committer | George Hazan <george.hazan@gmail.com> | 2025-04-18 16:12:38 +0300 |
commit | 3b3dea953dc54158db9cb35378852175e55c669d (patch) | |
tree | 5cf71e6fa5e4be13b0158f0ded6a61bec17847fc | |
parent | 536b626a052525f226165f3834f4ea06c6a7ad11 (diff) |
Teams: new format of subscription request + massive code cleaning
-rw-r--r-- | protocols/Teams/Teams.vcxproj | 4 | ||||
-rw-r--r-- | protocols/Teams/Teams.vcxproj.filters | 12 | ||||
-rw-r--r-- | protocols/Teams/src/requests/chatrooms.h | 2 | ||||
-rw-r--r-- | protocols/Teams/src/requests/profile.h | 33 | ||||
-rw-r--r-- | protocols/Teams/src/requests/subscriptions.h | 56 | ||||
-rw-r--r-- | protocols/Teams/src/stdafx.h | 5 | ||||
-rw-r--r-- | protocols/Teams/src/teams_chatrooms.cpp | 11 | ||||
-rw-r--r-- | protocols/Teams/src/teams_endpoint.cpp | 122 | ||||
-rw-r--r-- | protocols/Teams/src/teams_http.cpp | 32 | ||||
-rw-r--r-- | protocols/Teams/src/teams_menus.cpp | 7 | ||||
-rw-r--r-- | protocols/Teams/src/teams_mood.cpp | 146 | ||||
-rw-r--r-- | protocols/Teams/src/teams_proto.cpp | 2 | ||||
-rw-r--r-- | protocols/Teams/src/teams_proto.h | 23 | ||||
-rw-r--r-- | protocols/Teams/src/teams_server.cpp | 186 | ||||
-rw-r--r-- | protocols/Teams/src/teams_trouter.cpp | 4 | ||||
-rw-r--r-- | protocols/Teams/src/teams_utils.cpp | 17 |
16 files changed, 227 insertions, 435 deletions
diff --git a/protocols/Teams/Teams.vcxproj b/protocols/Teams/Teams.vcxproj index de27425cff..055768d98b 100644 --- a/protocols/Teams/Teams.vcxproj +++ b/protocols/Teams/Teams.vcxproj @@ -40,19 +40,17 @@ <ClCompile Include="src\teams_login.cpp" /> <ClCompile Include="src\teams_menus.cpp" /> <ClCompile Include="src\teams_messages.cpp" /> - <ClCompile Include="src\teams_mood.cpp" /> <ClCompile Include="src\teams_options.cpp" /> <ClCompile Include="src\teams_popups.cpp" /> <ClCompile Include="src\teams_profile.cpp" /> <ClCompile Include="src\teams_proto.cpp" /> <ClCompile Include="src\teams_search.cpp" /> + <ClCompile Include="src\teams_server.cpp" /> <ClCompile Include="src\teams_trouter.cpp" /> <ClCompile Include="src\teams_utils.cpp" /> <ClInclude Include="src\requests\chatrooms.h" /> <ClInclude Include="src\requests\history.h" /> - <ClInclude Include="src\requests\profile.h" /> <ClInclude Include="src\requests\search.h" /> - <ClInclude Include="src\requests\subscriptions.h" /> <ClInclude Include="src\resource.h" /> <ClInclude Include="src\stdafx.h" /> <ClInclude Include="src\teams_menus.h" /> diff --git a/protocols/Teams/Teams.vcxproj.filters b/protocols/Teams/Teams.vcxproj.filters index 458a991e68..3b9e085861 100644 --- a/protocols/Teams/Teams.vcxproj.filters +++ b/protocols/Teams/Teams.vcxproj.filters @@ -40,9 +40,6 @@ <ClCompile Include="src\teams_messages.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="src\teams_mood.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="src\teams_popups.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -61,6 +58,9 @@ <ClCompile Include="src\teams_trouter.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\teams_server.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\resource.h"> @@ -87,15 +87,9 @@ <ClInclude Include="src\requests\history.h"> <Filter>Header Files\Requests</Filter> </ClInclude> - <ClInclude Include="src\requests\profile.h"> - <Filter>Header Files\Requests</Filter> - </ClInclude> <ClInclude Include="src\requests\search.h"> <Filter>Header Files\Requests</Filter> </ClInclude> - <ClInclude Include="src\requests\subscriptions.h"> - <Filter>Header Files\Requests</Filter> - </ClInclude> </ItemGroup> <ItemGroup> <ResourceCompile Include="res\Resource.rc"> diff --git a/protocols/Teams/src/requests/chatrooms.h b/protocols/Teams/src/requests/chatrooms.h index cfef7dd8e0..d94f66a13a 100644 --- a/protocols/Teams/src/requests/chatrooms.h +++ b/protocols/Teams/src/requests/chatrooms.h @@ -40,7 +40,7 @@ struct CreateChatroomRequest : public AsyncHttpRequest struct GetChatMembersRequest : public AsyncHttpRequest { GetChatMembersRequest(const LIST<char> &ids, SESSION_INFO *si) : - AsyncHttpRequest(REQUEST_POST, HOST_PEOPLE, "/profiles", &CTeamsProto::OnGetChatMembers) + AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/profiles", &CTeamsProto::OnGetChatMembers) { JSONNode node, mris(JSON_ARRAY); mris.set_name("mris"); for (auto &it : ids) diff --git a/protocols/Teams/src/requests/profile.h b/protocols/Teams/src/requests/profile.h deleted file mode 100644 index a19772e3cc..0000000000 --- a/protocols/Teams/src/requests/profile.h +++ /dev/null @@ -1,33 +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_PROFILE_H_ -#define _SKYPE_REQUEST_PROFILE_H_ - -struct GetProfileRequest : public AsyncHttpRequest -{ - GetProfileRequest(CTeamsProto *ppro, MCONTACT hContact) : - AsyncHttpRequest(REQUEST_GET, HOST_API, 0, &CTeamsProto::LoadProfile) - { - m_szUrl.AppendFormat("/users/%s/profile", (hContact == 0) ? "self" : mir_urlEncode(ppro->getId(hContact))); - pUserInfo = (void *)hContact; - - AddHeader("Accept", "application/json"); - } -}; - -#endif //_SKYPE_REQUEST_PROFILE_H_ diff --git a/protocols/Teams/src/requests/subscriptions.h b/protocols/Teams/src/requests/subscriptions.h deleted file mode 100644 index 81f8cefdb7..0000000000 --- a/protocols/Teams/src/requests/subscriptions.h +++ /dev/null @@ -1,56 +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_SUBSCIPTIONS_H_ -#define _SKYPE_REQUEST_SUBSCIPTIONS_H_ - -struct CreateSubscriptionsRequest : public AsyncHttpRequest -{ - CreateSubscriptionsRequest() : - AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/users/ME/endpoints/SELF/subscriptions", &CTeamsProto::OnSubscriptionsCreated) - { - JSONNode interestedResources(JSON_ARRAY); interestedResources.set_name("interestedResources"); - interestedResources << CHAR_PARAM("", "/v1/users/ME/conversations/ALL/properties") - << CHAR_PARAM("", "/v1/users/ME/conversations/ALL/messages") - << CHAR_PARAM("", "/v1/users/ME/contacts/ALL") - << CHAR_PARAM("", "/v1/threads/ALL"); - - JSONNode node; - node << CHAR_PARAM("channelType", "httpLongPoll") << CHAR_PARAM("template", "raw") << interestedResources; - m_szParam = node.write().c_str(); - } -}; - -struct CreateContactsSubscriptionRequest : public AsyncHttpRequest -{ - CreateContactsSubscriptionRequest(const LIST<char> &skypenames) : - AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/users/ME/contacts") - { - JSONNode contacts(JSON_ARRAY); contacts.set_name("contacts"); - for (auto &it : skypenames) { - JSONNode contact; - contact << CHAR_PARAM("id", it); - contacts << contact; - } - - JSONNode node; - node << contacts; - m_szParam = node.write().c_str(); - } -}; - -#endif //_SKYPE_REQUEST_SUBSCIPTIONS_H_ diff --git a/protocols/Teams/src/stdafx.h b/protocols/Teams/src/stdafx.h index f5c0ba8236..72d39a9909 100644 --- a/protocols/Teams/src/stdafx.h +++ b/protocols/Teams/src/stdafx.h @@ -68,9 +68,9 @@ enum SkypeHost HOST_API, HOST_CONTACTS, HOST_DEFAULT, + HOST_DEFAULT_V2, HOST_GRAPH, HOST_LOGIN, - HOST_PEOPLE, HOST_TEAMS, HOST_PRESENCE, HOST_OTHER @@ -83,7 +83,6 @@ struct AsyncHttpRequest : public MTHttpRequest<CTeamsProto> AsyncHttpRequest(int type, SkypeHost host, LPCSTR url = nullptr, MTHttpRequestHandler pFunc = nullptr); - void AddRegister(CTeamsProto *ppro); void AddAuthentication(CTeamsProto *ppro); }; @@ -91,8 +90,6 @@ struct AsyncHttpRequest : public MTHttpRequest<CTeamsProto> #include "requests/chatrooms.h" #include "requests/history.h" -#include "requests/profile.h" #include "requests/search.h" -#include "requests/subscriptions.h" #endif //_COMMON_H_ diff --git a/protocols/Teams/src/teams_chatrooms.cpp b/protocols/Teams/src/teams_chatrooms.cpp index e4012e376f..87a3314965 100644 --- a/protocols/Teams/src/teams_chatrooms.cpp +++ b/protocols/Teams/src/teams_chatrooms.cpp @@ -494,7 +494,7 @@ class CSkypeGCCreateDlg : public CTeamsDlgBase CCtrlClc m_clc; public: - LIST<char> m_ContactsList; + OBJLIST<char> m_ContactsList; CSkypeGCCreateDlg(CTeamsProto *proto) : CTeamsDlgBase(proto, IDD_GC_CREATE), @@ -506,8 +506,6 @@ public: ~CSkypeGCCreateDlg() { - CTeamsProto::FreeList(m_ContactsList); - m_ContactsList.destroy(); } bool OnInitDialog() override @@ -522,14 +520,13 @@ public: bool OnApply() override { - for (auto &hContact : m_proto->AccContacts()) { + for (auto &hContact : m_proto->AccContacts()) if (!m_proto->isChatRoom(hContact)) if (HANDLE hItem = m_clc.FindContact(hContact)) if (m_clc.GetCheck(hItem)) - m_ContactsList.insert(m_proto->getId(hContact).Detach()); - } + m_ContactsList.insert(newStr(m_proto->getId(hContact))); - m_ContactsList.insert(m_proto->m_szSkypename.GetBuffer()); + m_ContactsList.insert(newStr(m_proto->m_szSkypename)); return true; } diff --git a/protocols/Teams/src/teams_endpoint.cpp b/protocols/Teams/src/teams_endpoint.cpp index c53c6f8eea..c29bc64ff9 100644 --- a/protocols/Teams/src/teams_endpoint.cpp +++ b/protocols/Teams/src/teams_endpoint.cpp @@ -76,135 +76,17 @@ void CTeamsProto::OnEndpointCreated(MHttpResponse *response, AsyncHttpRequest*) else if (name == "endpointId") { val.Replace("{", ""); val.Replace("}", ""); - m_szEndpoint = val.Detach(); + m_szEndpoint = val; } } } SetServerStatus(m_iDesiredStatus); StartTrouter(); - PushRequest(new CreateSubscriptionsRequest()); } void CTeamsProto::OnEndpointDeleted(MHttpResponse *, AsyncHttpRequest *) { - m_szEndpoint = nullptr; + m_szEndpoint.Empty(); m_szToken = nullptr; } - -void CTeamsProto::OnSubscriptionsCreated(MHttpResponse *response, AsyncHttpRequest*) -{ - if (response == nullptr) { - debugLogA(__FUNCTION__ ": failed to create subscription"); - ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); - SetStatus(ID_STATUS_OFFLINE); - return; - } - - SendPresence(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CTeamsProto::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest *) -{ - if (response == nullptr || response->body.IsEmpty()) { - ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); - SetStatus(ID_STATUS_OFFLINE); - return; - } - - LIST<char> skypenames(1); - for (auto &hContact : AccContacts()) - if (!isChatRoom(hContact)) - skypenames.insert(getId(hContact).Detach()); - - PushRequest(new CreateContactsSubscriptionRequest(skypenames)); - FreeList(skypenames); - skypenames.destroy(); - - ReceiveAvatar(0); - PushRequest(new AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/contacts", &CTeamsProto::LoadContactList)); - PushRequest(new SyncConversations()); - - JSONNode root = JSONNode::parse(response->body); - if (root) - m_szOwnSkypeId = UrlToSkypeId(root["selfLink"].as_string().c_str()).Detach(); - - 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->resultCode != 200) { - debugLogA(__FUNCTION__ ": failed to change status"); - ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); - SetStatus(ID_STATUS_OFFLINE); - return; - } - - int oldStatus = m_iStatus; - m_iStatus = m_iDesiredStatus; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); -} - -void CTeamsProto::SetServerStatus(int iStatus) -{ - const char *pszAvailability; - switch (iStatus) { - case ID_STATUS_OFFLINE: - pszAvailability = "Offline"; - break; - case ID_STATUS_NA: - case ID_STATUS_AWAY: - pszAvailability = "Away"; - break; - case ID_STATUS_DND: - pszAvailability = "DoNotDisturb"; - break; - case ID_STATUS_OCCUPIED: - pszAvailability = "Busy"; - break; - default: - pszAvailability = "Available"; - } - - JSONNode node(JSON_NODE); - node << CHAR_PARAM("availability", pszAvailability); - - auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_PRESENCE, "/me/forceavailability", &CTeamsProto::OnStatusChanged); - pReq->m_szParam = node.write().c_str(); - PushRequest(pReq); -} diff --git a/protocols/Teams/src/teams_http.cpp b/protocols/Teams/src/teams_http.cpp index 66043ed5ad..dab2532760 100644 --- a/protocols/Teams/src/teams_http.cpp +++ b/protocols/Teams/src/teams_http.cpp @@ -22,20 +22,25 @@ 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 = "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_BASE_HOST; break; case HOST_PRESENCE: m_szUrl = "presence." TEAMS_BASE_HOST "/v1"; break; + case HOST_DEFAULT_V2: + AddHeader("MS-IC3-Product", "Sfl"); + m_szUrl = "msgapi." TEAMS_BASE_HOST "/v2"; + m_host = HOST_DEFAULT; + break; + case HOST_DEFAULT: AddHeader("MS-IC3-Product", "Sfl"); - m_szUrl = "msgapi." TEAMS_BASE_HOST "/v1"; + m_szUrl = "msgapi." TEAMS_BASE_HOST "/v1"; break; } - AddHeader("User-Agent", NETLIB_USER_AGENT); + AddHeader("User-Agent", TEAMS_USER_AGENT); if (url) m_szUrl.Append(url); @@ -49,11 +54,6 @@ void AsyncHttpRequest::AddAuthentication(CTeamsProto *ppro) AddHeader("Authentication", CMStringA("skypetoken=") + ppro->m_szSkypeToken); } -void AsyncHttpRequest::AddRegister(CTeamsProto *ppro) -{ - AddHeader("RegistrationToken", CMStringA("registrationToken=") + ppro->m_szToken); -} - ///////////////////////////////////////////////////////////////////////////////////////// void CTeamsProto::StartQueue() @@ -115,17 +115,19 @@ MHttpResponse* CTeamsProto::DoSend(AsyncHttpRequest *pReq) pReq->AddHeader("X-MS-Client-Consumer-Type", "teams4life"); switch (pReq->m_host) { - case HOST_API: - case HOST_PEOPLE: case HOST_CONTACTS: + case HOST_DEFAULT: if (m_szSkypeToken) pReq->AddHeader("X-Skypetoken", m_szSkypeToken); pReq->AddHeader("Accept", "application/json"); - pReq->AddHeader("Origin", "https://web.skype.com"); - pReq->AddHeader("Referer", "https://web.skype.com/"); + pReq->AddHeader("X-Stratus-Caller", TEAMS_CLIENTINFO_NAME); + pReq->AddHeader("X-Stratus-Request", "abcd1234"); + pReq->AddHeader("Origin", "https://teams.live.com"); + pReq->AddHeader("Referer", "https://teams.live.com/"); break; + case HOST_API: case HOST_GRAPH: if (m_szSkypeToken) pReq->AddHeader("X-Skypetoken", m_szSkypeToken); @@ -148,12 +150,6 @@ MHttpResponse* CTeamsProto::DoSend(AsyncHttpRequest *pReq) pReq->flags |= NLHRF_NODUMP; #endif break; - - case HOST_DEFAULT: - if (m_szToken) - pReq->AddRegister(this); - pReq->AddHeader("Accept", "application/json, text/javascript"); - break; } debugLogA("Send request to %s", pReq->m_szUrl.c_str()); diff --git a/protocols/Teams/src/teams_menus.cpp b/protocols/Teams/src/teams_menus.cpp index 8605e30a52..e46d2174e1 100644 --- a/protocols/Teams/src/teams_menus.cpp +++ b/protocols/Teams/src/teams_menus.cpp @@ -89,11 +89,4 @@ void CTeamsProto::OnBuildProtoMenu() mi.position = 200000; mi.hIcolibItem = g_plugin.getIconHandle(IDI_CONFERENCE); Menu_AddProtoMenuItem(&mi, m_szModuleName); - - mi.pszService = "/SetMood"; - CreateProtoService(mi.pszService, &CTeamsProto::SvcSetMood); - mi.name.a = LPGEN("Set own mood"); - mi.position++; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_USERONLINE); - Menu_AddProtoMenuItem(&mi, m_szModuleName); } diff --git a/protocols/Teams/src/teams_mood.cpp b/protocols/Teams/src/teams_mood.cpp deleted file mode 100644 index fb366ac97a..0000000000 --- a/protocols/Teams/src/teams_mood.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* -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" - -struct -{ - const char *ss; - const wchar_t *defStatus; - int defIcon; -} -static moods[] = -{ - { "", LPGENW("None") }, - { "", LPGENW("Custom emoji") }, - { "brb", LPGENW("Be right back") }, - { "burger", LPGENW("Out for lunch") }, - { "wait", LPGENW("In meetings") }, - { "learn", LPGENW("At school") }, - { "movie", LPGENW("At the movies") }, - { "plane", LPGENW("Traveling") }, - { "party", LPGENW("Celebrating") }, - { "car", LPGENW("Driving") }, - { "skip", LPGENW("At the gym") }, - { "wfh", LPGENW("Working from home") }, -}; - -struct SetStatusMsgRequest : public AsyncHttpRequest -{ - SetStatusMsgRequest(CTeamsProto *ppro) : - AsyncHttpRequest(REQUEST_POST, HOST_API, "/users/self/profile/partial") - { - int m_iMood = ppro->m_iMood; - auto &pMood = moods[m_iMood]; - - JSONNode node, payload; - payload.set_name("payload"); - - CMStringW s1, s2; - switch (m_iMood) { - case 0: // none - s1 = ppro->m_wstrMoodMessage; - break; - case 1: // custom - s1.Format(L"(%x) %s", Utf16toUtf32(ppro->m_wstrMoodEmoji), (wchar_t *)ppro->m_wstrMoodMessage); - break; - default: - s1.Format(L"(%S) %s", pMood.ss, (wchar_t *)ppro->m_wstrMoodMessage); - break; - } - payload << WCHAR_PARAM("mood", s1); - - if (m_iMood > 1) - s2.Format(L"<ss type=\"%S\">(%S)</ss>%s", pMood.ss, pMood.ss, (wchar_t*)ppro->m_wstrMoodMessage); - else if (m_iMood == 1) { - int code = Utf16toUtf32(ppro->m_wstrMoodEmoji); - s2.Format(L"<ss type=\"%x\">(%x)</ss>%s", code, code, (wchar_t *)ppro->m_wstrMoodMessage); - } - - if (!s2.IsEmpty()) - payload << WCHAR_PARAM("richMood", s2); - - node << payload; - m_szParam = node.write().c_str(); - } -}; - -int getMoodIndex(const char *pszMood) -{ - for (auto &it : moods) - if (!mir_strcmpi(it.ss, pszMood)) - return int(&it - moods); - - return -1; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Mood dialog - -class CMoodDialog : public CTeamsDlgBase -{ - CCtrlEdit edtText, edtEmoji; - CCtrlCombo cmbMoods; - -public: - CMoodDialog(CTeamsProto *ppro) : - CTeamsDlgBase(ppro, IDD_MOOD), - edtText(this, IDC_MOOD_TEXT), - edtEmoji(this, IDC_MOOD_EMOJI), - cmbMoods(this, IDC_MOOD_COMBO) - { - CreateLink(edtText, ppro->m_wstrMoodMessage); - CreateLink(edtEmoji, ppro->m_wstrMoodEmoji); - - cmbMoods.OnChange = Callback(this, &CMoodDialog::onChangeSel_Mood); - } - - bool OnInitDialog() override - { - for (auto &it : moods) - cmbMoods.AddString(TranslateW(it.defStatus), int(&it - moods)); - cmbMoods.SetCurSel(m_proto->m_iMood); - onChangeSel_Mood(0); - return true; - } - - bool OnApply() override - { - m_proto->m_iMood = cmbMoods.GetCurSel(); - - CMStringA szSetting(FORMAT, "Mood%d", (int)m_proto->m_iMood); - m_proto->setWString(szSetting, m_proto->m_wstrMoodMessage); - - m_proto->PushRequest(new SetStatusMsgRequest(m_proto)); - return true; - } - - void onChangeSel_Mood(CCtrlCombo *) - { - int m_iMood = cmbMoods.GetCurSel(); - edtEmoji.Enable(m_iMood == 1); - - CMStringA szSetting(FORMAT, "Mood%d", m_iMood); - edtText.SetText(m_proto->getMStringW(szSetting)); - } -}; - -INT_PTR CTeamsProto::SvcSetMood(WPARAM, LPARAM) -{ - CMoodDialog(this).DoModal(); - return 0; -} diff --git a/protocols/Teams/src/teams_proto.cpp b/protocols/Teams/src/teams_proto.cpp index 16f2b6b0db..76fcc06505 100644 --- a/protocols/Teams/src/teams_proto.cpp +++ b/protocols/Teams/src/teams_proto.cpp @@ -207,7 +207,7 @@ int CTeamsProto::GetInfo(MCONTACT hContact, int) if (isChatRoom(hContact)) return 1; - PushRequest(new GetProfileRequest(this, hContact)); + GetProfileInfo(hContact); return 0; } diff --git a/protocols/Teams/src/teams_proto.h b/protocols/Teams/src/teams_proto.h index 7cf8952023..fdbee4d39e 100644 --- a/protocols/Teams/src/teams_proto.h +++ b/protocols/Teams/src/teams_proto.h @@ -165,8 +165,8 @@ public: // other data int m_iPollingId, m_iMessageId = 1; - ptrA m_szToken, m_szEndpoint, m_szOwnSkypeId; - CMStringA m_szSkypename, m_szMyname, m_szSkypeToken; + ptrA m_szToken, m_szOwnSkypeId; + CMStringA m_szSkypename, m_szMyname, m_szSkypeToken, m_szEndpoint; MCONTACT m_hMyContact; __forceinline CMStringA getId(MCONTACT hContact) { @@ -298,14 +298,16 @@ private: bool ParseMessage(const JSONNode &node, DB::EventInfo &dbei); - // utils - template <typename T> - __inline static void FreeList(const LIST<T> &lst) - { - for (auto &it : lst) - mir_free(it); - } + // server requests + void GetProfileInfo(MCONTACT hContact); + + void SetServerStatus(int iStatus); + void CreateSubscription(); + void CreateContactSubscription(); + + + // utils __forceinline bool IsOnline() const { return (m_iStatus > ID_STATUS_OFFLINE); } @@ -320,8 +322,6 @@ private: static time_t IsoToUnixTime(const std::string &stamp); - void SetServerStatus(int iStatus); - void ShowNotification(const wchar_t *message, MCONTACT hContact = NULL); void ShowNotification(const wchar_t *caption, const wchar_t *message, MCONTACT hContact = NULL, int type = 0); static bool IsFileExists(std::wstring path); @@ -340,7 +340,6 @@ private: INT_PTR __cdecl SvcLoadHistory(WPARAM hContact, LPARAM); INT_PTR __cdecl SvcEmptyHistory(WPARAM hContact, LPARAM); INT_PTR __cdecl SvcCreateChat(WPARAM, LPARAM); - INT_PTR __cdecl SvcSetMood(WPARAM, LPARAM); INT_PTR __cdecl ParseSkypeUriService(WPARAM, LPARAM lParam); // trouter diff --git a/protocols/Teams/src/teams_server.cpp b/protocols/Teams/src/teams_server.cpp new file mode 100644 index 0000000000..ef7699d5c7 --- /dev/null +++ b/protocols/Teams/src/teams_server.cpp @@ -0,0 +1,186 @@ +/* +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::OnCapabilitiesSended(MHttpResponse *response, AsyncHttpRequest *) +{ + if (response == nullptr || response->body.IsEmpty()) { + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + SetStatus(ID_STATUS_OFFLINE); + return; + } + + CreateContactSubscription(); + ReceiveAvatar(0); + PushRequest(new AsyncHttpRequest(REQUEST_GET, HOST_CONTACTS, "/users/SELF/contacts", &CTeamsProto::LoadContactList)); + PushRequest(new SyncConversations()); + + JSONNode root = JSONNode::parse(response->body); + if (root) + m_szOwnSkypeId = UrlToSkypeId(root["selfLink"].as_string().c_str()).Detach(); + + GetProfileInfo(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(); + pReq->AddHeader("RegistrationToken", CMStringA("registrationToken=") + m_szToken); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::OnStatusChanged(MHttpResponse *response, AsyncHttpRequest *) +{ + if (response == nullptr || response->resultCode != 200) { + debugLogA(__FUNCTION__ ": failed to change status"); + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + SetStatus(ID_STATUS_OFFLINE); + return; + } + + int oldStatus = m_iStatus; + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); +} + +void CTeamsProto::SetServerStatus(int iStatus) +{ + const char *pszAvailability; + switch (iStatus) { + case ID_STATUS_OFFLINE: + pszAvailability = "Offline"; + break; + case ID_STATUS_NA: + case ID_STATUS_AWAY: + pszAvailability = "Away"; + break; + case ID_STATUS_DND: + pszAvailability = "DoNotDisturb"; + break; + case ID_STATUS_OCCUPIED: + pszAvailability = "Busy"; + break; + default: + pszAvailability = "Available"; + } + + JSONNode node(JSON_NODE); + node << CHAR_PARAM("availability", pszAvailability); + + auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_PRESENCE, "/me/forceavailability", &CTeamsProto::OnStatusChanged); + pReq->m_szParam = node.write().c_str(); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::OnSubscriptionsCreated(MHttpResponse *response, AsyncHttpRequest *) +{ + if (response == nullptr) { + debugLogA(__FUNCTION__ ": failed to create subscription"); + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + SetStatus(ID_STATUS_OFFLINE); + } + else SendPresence(); +} + +void CTeamsProto::CreateSubscription() +{ + JSONNode interestedResources(JSON_ARRAY); interestedResources.set_name("interestedResources"); + interestedResources << CHAR_PARAM("", "/v1/users/ME/conversations/ALL/properties") + << CHAR_PARAM("", "/v1/users/ME/conversations/ALL/messages") + << CHAR_PARAM("", "/v1/users/ME/contacts/ALL") + << CHAR_PARAM("", "/v1/threads/ALL"); + + JSONNode subscription, trouter; + subscription << CHAR_PARAM("channelType", "HttpLongPoll") << interestedResources; + trouter << CHAR_PARAM("channelType", "TrouterPush") << CHAR_PARAM("longPollUrl", m_szTrouterSurl) << interestedResources; + + JSONNode subscriptions(JSON_ARRAY); subscriptions.set_name("subscriptions"); + subscriptions << subscription << trouter; + + JSONNode node; + node << INT_PARAM("startingTimeSpan", 0) << CHAR_PARAM("endpointFeatures", "Agent,Presence2015,MessageProperties,CustomUserProperties,NotificationStream,SupportsSkipRosterFromThreads") + << subscriptions; + + auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_DEFAULT_V2, "/users/ME/endpoints/" + m_szEndpoint, &CTeamsProto::OnSubscriptionsCreated); + pReq->m_szParam = node.write().c_str(); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::CreateContactSubscription() +{ + OBJLIST<char> skypenames(1); + for (auto &hContact : AccContacts()) + if (!isChatRoom(hContact)) + skypenames.insert(newStr(getId(hContact))); + + JSONNode contacts(JSON_ARRAY); contacts.set_name("contacts"); + for (auto &it : skypenames) { + JSONNode contact; + contact << CHAR_PARAM("id", it); + contacts << contact; + } + + JSONNode node; + node << contacts; + + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_DEFAULT, "/users/ME/contacts"); + pReq->m_szParam = node.write().c_str(); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::GetProfileInfo(MCONTACT hContact) +{ + auto *pReq = new AsyncHttpRequest(REQUEST_GET, HOST_API, 0, &CTeamsProto::LoadProfile); + pReq->m_szUrl.AppendFormat("/users/%s/profile", (hContact == 0) ? "self" : mir_urlEncode(getId(hContact))); + pReq->pUserInfo = (void *)hContact; + PushRequest(pReq); +} diff --git a/protocols/Teams/src/teams_trouter.cpp b/protocols/Teams/src/teams_trouter.cpp index a969d35bfe..d466b98ad3 100644 --- a/protocols/Teams/src/teams_trouter.cpp +++ b/protocols/Teams/src/teams_trouter.cpp @@ -62,12 +62,14 @@ void CTeamsProto::OnTrouterInfo(MHttpResponse *response, AsyncHttpRequest *) if (!ccid.IsEmpty()) pReq << CHAR_PARAM("ccid", ccid); PushRequest(pReq); + + CreateSubscription(); } 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->m_szUrl.AppendFormat("?epid=%s", m_szEndpoint.c_str()); pReq->AddHeader("x-skypetoken", m_szSkypeToken); pReq->flags |= NLHRF_NODUMPHEADERS; PushRequest(pReq); diff --git a/protocols/Teams/src/teams_utils.cpp b/protocols/Teams/src/teams_utils.cpp index c409419054..8d158dfd4d 100644 --- a/protocols/Teams/src/teams_utils.cpp +++ b/protocols/Teams/src/teams_utils.cpp @@ -483,23 +483,6 @@ CMStringW CTeamsProto::RemoveHtml(const CMStringW &data, bool bCheckSS) } } - if (c == '(' && inSS) { - int iEnd = data.Find(')', i); - if (iEnd != -1) { - CMStringW ss(data.Mid(i + 1, iEnd - i - 1)); - uint32_t code = getMoodIndex(T2Utf(ss)); - if (code != -1) - m_iMood = code; - else if (1 == swscanf(ss, L"%x_", &code)) { - Utf32toUtf16(code, new_string); - m_wstrMoodEmoji = new_string; - } - - i = iEnd; - continue; - } - } - new_string.AppendChar(c); } |