summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2025-04-18 16:12:38 +0300
committerGeorge Hazan <george.hazan@gmail.com>2025-04-18 16:12:38 +0300
commit3b3dea953dc54158db9cb35378852175e55c669d (patch)
tree5cf71e6fa5e4be13b0158f0ded6a61bec17847fc
parent536b626a052525f226165f3834f4ea06c6a7ad11 (diff)
Teams: new format of subscription request + massive code cleaning
-rw-r--r--protocols/Teams/Teams.vcxproj4
-rw-r--r--protocols/Teams/Teams.vcxproj.filters12
-rw-r--r--protocols/Teams/src/requests/chatrooms.h2
-rw-r--r--protocols/Teams/src/requests/profile.h33
-rw-r--r--protocols/Teams/src/requests/subscriptions.h56
-rw-r--r--protocols/Teams/src/stdafx.h5
-rw-r--r--protocols/Teams/src/teams_chatrooms.cpp11
-rw-r--r--protocols/Teams/src/teams_endpoint.cpp122
-rw-r--r--protocols/Teams/src/teams_http.cpp32
-rw-r--r--protocols/Teams/src/teams_menus.cpp7
-rw-r--r--protocols/Teams/src/teams_mood.cpp146
-rw-r--r--protocols/Teams/src/teams_proto.cpp2
-rw-r--r--protocols/Teams/src/teams_proto.h23
-rw-r--r--protocols/Teams/src/teams_server.cpp186
-rw-r--r--protocols/Teams/src/teams_trouter.cpp4
-rw-r--r--protocols/Teams/src/teams_utils.cpp17
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);
}