diff options
Diffstat (limited to 'protocols')
81 files changed, 928 insertions, 510 deletions
diff --git a/protocols/CloudFile/res/dropbox.ico b/protocols/CloudFile/res/dropbox.ico Binary files differindex 8a286d7f32..09d2721244 100644 --- a/protocols/CloudFile/res/dropbox.ico +++ b/protocols/CloudFile/res/dropbox.ico diff --git a/protocols/CloudFile/res/gdrive.ico b/protocols/CloudFile/res/gdrive.ico Binary files differindex a34123e95b..3655f72583 100644 --- a/protocols/CloudFile/res/gdrive.ico +++ b/protocols/CloudFile/res/gdrive.ico diff --git a/protocols/CloudFile/res/onedrive.ico b/protocols/CloudFile/res/onedrive.ico Binary files differindex 502cee9bb7..94c8ab8240 100644 --- a/protocols/CloudFile/res/onedrive.ico +++ b/protocols/CloudFile/res/onedrive.ico diff --git a/protocols/CloudFile/res/upload.ico b/protocols/CloudFile/res/upload.ico Binary files differindex b51e87ed35..09f224a20e 100644 --- a/protocols/CloudFile/res/upload.ico +++ b/protocols/CloudFile/res/upload.ico diff --git a/protocols/CloudFile/res/yadisk.ico b/protocols/CloudFile/res/yadisk.ico Binary files differindex b3a5ac69b4..31d802c51f 100644 --- a/protocols/CloudFile/res/yadisk.ico +++ b/protocols/CloudFile/res/yadisk.ico diff --git a/protocols/Discord/proto_discord/res/Away.ico b/protocols/Discord/proto_discord/res/Away.ico Binary files differindex 844c1d4b3a..46dccb19ca 100644 --- a/protocols/Discord/proto_discord/res/Away.ico +++ b/protocols/Discord/proto_discord/res/Away.ico diff --git a/protocols/Discord/proto_discord/res/DND.ico b/protocols/Discord/proto_discord/res/DND.ico Binary files differindex 6341c0e08c..805dd75622 100644 --- a/protocols/Discord/proto_discord/res/DND.ico +++ b/protocols/Discord/proto_discord/res/DND.ico diff --git a/protocols/Discord/proto_discord/res/Invisible.ico b/protocols/Discord/proto_discord/res/Invisible.ico Binary files differindex 7d34d4ca58..d168845ae6 100644 --- a/protocols/Discord/proto_discord/res/Invisible.ico +++ b/protocols/Discord/proto_discord/res/Invisible.ico diff --git a/protocols/Discord/proto_discord/res/Offline.ico b/protocols/Discord/proto_discord/res/Offline.ico Binary files differindex f2ec365064..7473a3b968 100644 --- a/protocols/Discord/proto_discord/res/Offline.ico +++ b/protocols/Discord/proto_discord/res/Offline.ico diff --git a/protocols/Discord/proto_discord/res/Online.ico b/protocols/Discord/proto_discord/res/Online.ico Binary files differindex 94f4d0d8bd..1d3efde242 100644 --- a/protocols/Discord/proto_discord/res/Online.ico +++ b/protocols/Discord/proto_discord/res/Online.ico diff --git a/protocols/Discord/res/discord.ico b/protocols/Discord/res/discord.ico Binary files differindex c2830ed132..1d3efde242 100644 --- a/protocols/Discord/res/discord.ico +++ b/protocols/Discord/res/discord.ico diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp index c038234aec..e3bf1b307c 100644 --- a/protocols/Discord/src/gateway.cpp +++ b/protocols/Discord/src/gateway.cpp @@ -25,9 +25,7 @@ bool CDiscordProto::GatewaySend(const JSONNode &pRoot) if (!m_bConnected)
return false;
- json_string szText = pRoot.write();
- debugLogA("Gateway send: %s", szText.c_str());
- m_ws.sendText(szText.c_str());
+ m_ws.sendText(pRoot.write().c_str());
return true;
}
diff --git a/protocols/Discord/src/voice_client.cpp b/protocols/Discord/src/voice_client.cpp index 1e20a93723..99d7668d74 100644 --- a/protocols/Discord/src/voice_client.cpp +++ b/protocols/Discord/src/voice_client.cpp @@ -61,9 +61,7 @@ void CDiscordVoiceCall::write(int op, JSONNode &d) JSONNode payload; payload << INT_PARAM("op", op) << d; - auto json = payload.write(); - ppro->debugLogA("Voice JSON sent: %s", json.c_str()); mir_cslock lck(m_cs); m_ws->sendText(json.c_str()); diff --git a/protocols/ICQCorp/CMakeLists.txt b/protocols/ICQCorp/CMakeLists.txt index 9e3d58ba26..d18898a34e 100644 --- a/protocols/ICQCorp/CMakeLists.txt +++ b/protocols/ICQCorp/CMakeLists.txt @@ -12,4 +12,5 @@ file(GLOB SOURCES "src/*.h" "res/*.rc" "src/user.cpp" ) set(TARGET ICQCorp) -include(${CMAKE_SOURCE_DIR}/cmake/plugin.cmake)
\ No newline at end of file +include(${CMAKE_SOURCE_DIR}/cmake/plugin.cmake) +add_subdirectory(proto_icq) diff --git a/protocols/ICQCorp/Proto_icq/CMakeLists.txt b/protocols/ICQCorp/Proto_icq/CMakeLists.txt new file mode 100644 index 0000000000..596ac21624 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/CMakeLists.txt @@ -0,0 +1,2 @@ +set(TARGET Proto_ICQ) +include(${CMAKE_SOURCE_DIR}/cmake/icons.cmake)
\ No newline at end of file diff --git a/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj b/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj new file mode 100644 index 0000000000..21763bbc0b --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectName>Proto_ICQ</ProjectName> + <ProjectGuid>{DB3B0449-E576-4BBB-8B08-AB9E914D39CA}</ProjectGuid> + </PropertyGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(ProjectDir)..\..\..\build\vc.common\icons.props" /> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj.filters b/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj.filters new file mode 100644 index 0000000000..28f81e7f1b --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/Proto_ICQ.vcxproj.filters @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(ProjectDir)..\..\..\build\vc.common\common.filters" /> +</Project>
\ No newline at end of file diff --git a/protocols/ICQCorp/Proto_icq/res/Away.ico b/protocols/ICQCorp/Proto_icq/res/Away.ico Binary files differnew file mode 100644 index 0000000000..248a3e9916 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Away.ico diff --git a/protocols/ICQCorp/Proto_icq/res/DND.ico b/protocols/ICQCorp/Proto_icq/res/DND.ico Binary files differnew file mode 100644 index 0000000000..4833160eac --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/DND.ico diff --git a/protocols/ICQCorp/Proto_icq/res/FFC.ico b/protocols/ICQCorp/Proto_icq/res/FFC.ico Binary files differnew file mode 100644 index 0000000000..e7ec4d3ae2 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/FFC.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Invisible.ico b/protocols/ICQCorp/Proto_icq/res/Invisible.ico Binary files differnew file mode 100644 index 0000000000..6a337c2926 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Invisible.ico diff --git a/protocols/ICQCorp/Proto_icq/res/NA.ico b/protocols/ICQCorp/Proto_icq/res/NA.ico Binary files differnew file mode 100644 index 0000000000..ec0621dc9f --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/NA.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Occupied.ico b/protocols/ICQCorp/Proto_icq/res/Occupied.ico Binary files differnew file mode 100644 index 0000000000..04ea2a5855 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Occupied.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Offline.ico b/protocols/ICQCorp/Proto_icq/res/Offline.ico Binary files differnew file mode 100644 index 0000000000..af862168cd --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Offline.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Online.ico b/protocols/ICQCorp/Proto_icq/res/Online.ico Binary files differnew file mode 100644 index 0000000000..2e33305a76 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Online.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Phone.ico b/protocols/ICQCorp/Proto_icq/res/Phone.ico Binary files differnew file mode 100644 index 0000000000..74c80b66ed --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Phone.ico diff --git a/protocols/ICQCorp/Proto_icq/res/Proto_ICQ.rc b/protocols/ICQCorp/Proto_icq/res/Proto_ICQ.rc new file mode 100644 index 0000000000..33d8faf243 --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/res/Proto_ICQ.rc @@ -0,0 +1,77 @@ +// Microsoft Visual C++ generated resource script. +// +#include "..\src\resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Russian (Russia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\\src\\resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "Offline.ico" +IDI_ICON2 ICON "Online.ico" +IDI_ICON3 ICON "Away.ico" +IDI_ICON4 ICON "Invisible.ico" +IDI_ICON5 ICON "NA.ico" +IDI_ICON6 ICON "DND.ico" +IDI_ICON7 ICON "Occupied.ico" +IDI_ICON8 ICON "FFC.ico" +IDI_ICON9 ICON "Phone.ico" +#endif // Russian (Russia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/ICQCorp/Proto_icq/src/resource.h b/protocols/ICQCorp/Proto_icq/src/resource.h new file mode 100644 index 0000000000..c74e04f59e --- /dev/null +++ b/protocols/ICQCorp/Proto_icq/src/resource.h @@ -0,0 +1,24 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Proto_ICQ.rc +// +#define IDI_ICON1 105 +#define IDI_ICON2 104 +#define IDI_ICON3 128 +#define IDI_ICON4 130 +#define IDI_ICON5 131 +#define IDI_ICON6 158 +#define IDI_ICON7 159 +#define IDI_ICON8 129 +#define IDI_ICON9 1002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 110 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/SkypeWeb/SkypeWeb.vcxproj b/protocols/SkypeWeb/SkypeWeb.vcxproj index f44147ab8f..c7cda08954 100644 --- a/protocols/SkypeWeb/SkypeWeb.vcxproj +++ b/protocols/SkypeWeb/SkypeWeb.vcxproj @@ -57,7 +57,6 @@ <ClInclude Include="src\requests\history.h" />
<ClInclude Include="src\requests\login.h" />
<ClInclude Include="src\requests\oauth.h" />
- <ClInclude Include="src\requests\poll.h" />
<ClInclude Include="src\requests\profile.h" />
<ClInclude Include="src\requests\search.h" />
<ClInclude Include="src\requests\status.h" />
diff --git a/protocols/SkypeWeb/SkypeWeb.vcxproj.filters b/protocols/SkypeWeb/SkypeWeb.vcxproj.filters index e6d0bb2fc3..15d9199310 100644 --- a/protocols/SkypeWeb/SkypeWeb.vcxproj.filters +++ b/protocols/SkypeWeb/SkypeWeb.vcxproj.filters @@ -96,9 +96,6 @@ <ClInclude Include="src\requests\oauth.h">
<Filter>Header Files\Requests</Filter>
</ClInclude>
- <ClInclude Include="src\requests\poll.h">
- <Filter>Header Files\Requests</Filter>
- </ClInclude>
<ClInclude Include="src\requests\profile.h">
<Filter>Header Files\Requests</Filter>
</ClInclude>
diff --git a/protocols/SkypeWeb/src/requests/poll.h b/protocols/SkypeWeb/src/requests/poll.h deleted file mode 100644 index bf1850fd27..0000000000 --- a/protocols/SkypeWeb/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(CSkypeProto *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/SkypeWeb/src/skype_polling.cpp b/protocols/SkypeWeb/src/skype_polling.cpp index ed7b50938e..2821f06d34 100644 --- a/protocols/SkypeWeb/src/skype_polling.cpp +++ b/protocols/SkypeWeb/src/skype_polling.cpp @@ -27,14 +27,34 @@ void CSkypeProto::PollingThread(void *) 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()));
+ AsyncHttpRequest req(REQUEST_POST, HOST_DEFAULT, "/users/ME/endpoints/" + mir_urlEncode(m_szId) + "/subscriptions/0/poll");
+ req.flags |= NLHRF_PERSISTENT;
+ req.timeout = 120000;
+ req.nlc = m_hPollingConn;
+
+ if (m_iPollingId != -1)
+ req.m_szUrl.AppendFormat("?ackId=%d", m_iPollingId);
+
+ req.AddHeader("Referer", "https://web.skype.com/main");
+ req.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");
+ req.AddHeader("Accept", "application/json");
+ req.AddHeader("Accept-Language", "en, C");
+
+ NLHR_PTR response(DoSend(&req));
if (m_isTerminated || m_szId == nullptr)
break;
- if (response == nullptr || response->resultCode != 200) {
- m_hPollingConn = nullptr;
+ // no network?..
+ if (response == nullptr)
+ break;
+
+ if (response->resultCode != 200) {
+ auto reply = JSONNode::parse(response->body);
+ if (reply && reply["message"]["errorCode"] == 729) // endpoint broken, log off
+ break;
+
+ Sleep(200);
+ m_hPollingConn = response->nlc;
continue;
}
diff --git a/protocols/SkypeWeb/src/stdafx.h b/protocols/SkypeWeb/src/stdafx.h index ef94f3040c..263aefd914 100644 --- a/protocols/SkypeWeb/src/stdafx.h +++ b/protocols/SkypeWeb/src/stdafx.h @@ -119,7 +119,6 @@ struct AsyncHttpRequest : public MTHttpRequest<CSkypeProto> #include "requests/history.h"
#include "requests/login.h"
#include "requests/oauth.h"
-#include "requests/poll.h"
#include "requests/profile.h"
#include "requests/search.h"
#include "requests/status.h"
diff --git a/protocols/Steam/proto_steam/res/Invisible.ico b/protocols/Steam/proto_steam/res/Invisible.ico Binary files differindex f920c04ed2..68e4a93641 100644 --- a/protocols/Steam/proto_steam/res/Invisible.ico +++ b/protocols/Steam/proto_steam/res/Invisible.ico diff --git a/protocols/Steam/src/steam_login.cpp b/protocols/Steam/src/steam_login.cpp index 1235130542..669d164351 100644 --- a/protocols/Steam/src/steam_login.cpp +++ b/protocols/Steam/src/steam_login.cpp @@ -159,7 +159,7 @@ INT_PTR CALLBACK CSteamProto::EnterEmailCode(void *param) ppro->SendConfirmationCode(true, T2Utf(es.ptszResult)); mir_free(es.ptszResult); } - else ppro->Logout(); + else ppro->m_ws->terminate(); return 0; } @@ -174,7 +174,7 @@ INT_PTR CALLBACK CSteamProto::EnterTotpCode(void *param) ppro->SendConfirmationCode(false, T2Utf(es.ptszResult)); mir_free(es.ptszResult); } - else ppro->Logout(); + else ppro->m_ws->terminate(); return 0; } diff --git a/protocols/Teams/Teams.vcxproj b/protocols/Teams/Teams.vcxproj index ad579a582d..436f8c746d 100644 --- a/protocols/Teams/Teams.vcxproj +++ b/protocols/Teams/Teams.vcxproj @@ -42,11 +42,11 @@ <ClCompile Include="src\teams_messages.cpp" /> <ClCompile Include="src\teams_mood.cpp" /> <ClCompile Include="src\teams_options.cpp" /> - <ClCompile Include="src\teams_polling.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_trouter.cpp" /> <ClCompile Include="src\teams_utils.cpp" /> <ClInclude Include="src\resource.h" /> <ClInclude Include="src\stdafx.h" /> diff --git a/protocols/Teams/Teams.vcxproj.filters b/protocols/Teams/Teams.vcxproj.filters index b2582eb02b..c15ff6085c 100644 --- a/protocols/Teams/Teams.vcxproj.filters +++ b/protocols/Teams/Teams.vcxproj.filters @@ -43,9 +43,6 @@ <ClCompile Include="src\teams_mood.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="src\teams_polling.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_search.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\teams_trouter.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\resource.h"> diff --git a/protocols/Teams/proto_teams/res/Away.ico b/protocols/Teams/proto_teams/res/Away.ico Binary files differindex 2faf6635bd..a6b544e597 100644 --- a/protocols/Teams/proto_teams/res/Away.ico +++ b/protocols/Teams/proto_teams/res/Away.ico diff --git a/protocols/Teams/proto_teams/res/DND.ico b/protocols/Teams/proto_teams/res/DND.ico Binary files differindex b38ab27226..5919b2e563 100644 --- a/protocols/Teams/proto_teams/res/DND.ico +++ b/protocols/Teams/proto_teams/res/DND.ico diff --git a/protocols/Teams/proto_teams/res/Invisible.ico b/protocols/Teams/proto_teams/res/Invisible.ico Binary files differindex 49b66a79fe..673f13a81a 100644 --- a/protocols/Teams/proto_teams/res/Invisible.ico +++ b/protocols/Teams/proto_teams/res/Invisible.ico 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" diff --git a/protocols/Weather/res/resource.rc b/protocols/Weather/res/resource.rc index b03db42abc..8c6e6227d8 100644 --- a/protocols/Weather/res/resource.rc +++ b/protocols/Weather/res/resource.rc @@ -182,7 +182,8 @@ BEGIN CONTROL "History Log",IDC_TM7,"MButtonClass",WS_TABSTOP,2,221,77,9,WS_EX_WINDOWEDGE | WS_EX_NOACTIVATE | 0x10000000L EDITTEXT IDC_HTEXT,80,219,183,12,ES_AUTOHSCROLL GROUPBOX "Variable List",IDC_STATIC,207,14,99,191 - LTEXT "",IDC_VARLIST,213,25,86,157 + CONTROL "",IDC_VARLIST,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,213,25,86,157 + CONTROL "More Variables",IDC_MORE,"MButtonClass",WS_TABSTOP,216,187,81,15,WS_EX_WINDOWEDGE | WS_EX_NOACTIVATE | 0x10000000L CONTROL "Reset",IDC_RESET,"MButtonClass",WS_TABSTOP,266,208,40,21,WS_EX_WINDOWEDGE | WS_EX_NOACTIVATE | 0x10000000L EDITTEXT IDC_BTITLE2,80,28,125,12,ES_AUTOHSCROLL CONTROL "Status Message",IDC_TM8,"MButtonClass",WS_TABSTOP | 0x100,2,29,77,9,WS_EX_WINDOWEDGE | WS_EX_NOACTIVATE | 0x10000000L diff --git a/protocols/Weather/src/proto.h b/protocols/Weather/src/proto.h index e83fee46eb..e1115b65e3 100644 --- a/protocols/Weather/src/proto.h +++ b/protocols/Weather/src/proto.h @@ -151,7 +151,6 @@ class CWeatherProto : public PROTO<CWeatherProto> // data void ConvertDataValue(WIDATAITEM *UpdateData); void EraseAllInfo(void); - void GetStationID(MCONTACT hContact, wchar_t *id, int idlen); WEATHERINFO LoadWeatherInfo(MCONTACT hContact); MHttpResponse* RunQuery(const wchar_t *id, int days); @@ -182,6 +181,7 @@ class CWeatherProto : public PROTO<CWeatherProto> int __cdecl OptInit(WPARAM, LPARAM); CMStringW GetTextValue(int c); + void GetVarsDescr(CMStringW &str); // popups int WPShowMessage(const wchar_t *lpzText, int kind); diff --git a/protocols/Weather/src/resource.h b/protocols/Weather/src/resource.h index e53877d0ee..809c94857b 100644 --- a/protocols/Weather/src/resource.h +++ b/protocols/Weather/src/resource.h @@ -109,6 +109,7 @@ #define IDC_INFO10 2091 #define IDC_INFO12 2092 #define IDC_INFO13 2093 +#define IDC_MORE 2094 #define IDC_MOREDETAIL 2095 #define IDC_DATALIST 2096 #define IDC_MUPDATE 2097 diff --git a/protocols/Weather/src/stdafx.h b/protocols/Weather/src/stdafx.h index eec03bd13e..cf1f3240a3 100644 --- a/protocols/Weather/src/stdafx.h +++ b/protocols/Weather/src/stdafx.h @@ -146,7 +146,7 @@ void TrimString(wchar_t *str); void ConvertBackslashes(char *str);
char *GetSearchStr(char *dis);
-wchar_t *GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t* str);
+CMStringW GetDisplay(WEATHERINFO *w, const wchar_t *dis);
wchar_t *GetError(int code);
diff --git a/protocols/Weather/src/weather_addstn.cpp b/protocols/Weather/src/weather_addstn.cpp index 1bdfb9b49d..026b9f8c67 100644 --- a/protocols/Weather/src/weather_addstn.cpp +++ b/protocols/Weather/src/weather_addstn.cpp @@ -88,7 +88,7 @@ MCONTACT CWeatherProto::AddToList(int, PROTOSEARCHRESULT *psr) // if no default station is found, set the new contact as default station if (opt.Default[0] == 0) { - GetStationID(hContact, opt.Default, _countof(opt.Default)); + wcsncpy_s(opt.Default, getMStringW(hContact, "ID"), _countof(opt.Default)); opt.DefStn = hContact; ptrW wszNick(getWStringA(hContact, "Nick")); diff --git a/protocols/Weather/src/weather_contacts.cpp b/protocols/Weather/src/weather_contacts.cpp index 2661c1b20e..2f5a08f4ef 100644 --- a/protocols/Weather/src/weather_contacts.cpp +++ b/protocols/Weather/src/weather_contacts.cpp @@ -53,13 +53,12 @@ INT_PTR CWeatherProto::ViewLog(WPARAM wParam, LPARAM lParam) // read complete forecast // wParam = current contact -INT_PTR CWeatherProto::LoadForecast(WPARAM wParam, LPARAM) +INT_PTR CWeatherProto::LoadForecast(WPARAM hContact, LPARAM) { - wchar_t id[256]; - GetStationID(wParam, id, _countof(id)); - if (id[0] != 0) { + CMStringW wszID(getMStringW(hContact, "ID")); + if (!wszID.IsEmpty()) { // set the url and open the webpage - CMStringA szUrl("https://www.visualcrossing.com/weather-forecast/" + mir_urlEncode(T2Utf(id)) + "/metric"); + CMStringA szUrl("https://www.visualcrossing.com/weather-forecast/" + mir_urlEncode(T2Utf(wszID)) + "/metric"); Utils_OpenUrl(szUrl); } return 0; @@ -69,13 +68,12 @@ INT_PTR CWeatherProto::LoadForecast(WPARAM wParam, LPARAM) // load weather map // wParam = current contact -INT_PTR CWeatherProto::WeatherMap(WPARAM wParam, LPARAM) +INT_PTR CWeatherProto::WeatherMap(WPARAM hContact, LPARAM) { - wchar_t id[256]; - GetStationID(wParam, id, _countof(id)); - if (id[0] != 0) { + CMStringW wszID(getMStringW(hContact, "ID")); + if (!wszID.IsEmpty()) { // set the url and open the webpage - CMStringA szUrl("https://www.visualcrossing.com/weather-history/" + mir_urlEncode(T2Utf(id)) + "/metric"); + CMStringA szUrl("https://www.visualcrossing.com/weather-history/" + mir_urlEncode(T2Utf(wszID)) + "/metric"); Utils_OpenUrl(szUrl); } diff --git a/protocols/Weather/src/weather_conv.cpp b/protocols/Weather/src/weather_conv.cpp index d9f35264b0..4d2c79f0ce 100644 --- a/protocols/Weather/src/weather_conv.cpp +++ b/protocols/Weather/src/weather_conv.cpp @@ -399,7 +399,7 @@ char* GetSearchStr(char *dis) // dis = the string to parse // return value = the parsed string -wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t *str) +CMStringW GetDisplay(WEATHERINFO *w, const wchar_t *dis) { wchar_t lpzDate[32], chr; char name[256], temp[2]; @@ -407,7 +407,7 @@ wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t *str) size_t i; // Clear the string - str[0] = 0; + CMStringW str; // looking character by character for (i = 0; i < mir_wstrlen(dis); i++) { @@ -416,10 +416,10 @@ wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t *str) i++; chr = dis[i]; switch (chr) { - case '%': mir_wstrcat(str, L"%"); break; - case 't': mir_wstrcat(str, L"\t"); break; - case 'n': mir_wstrcat(str, L"\r\n"); break; - case '\\': mir_wstrcat(str, L"\\"); break; + case '%': str.Append(L"%"); break; + case 't': str.Append(L"\t"); break; + case 'n': str.Append(L"\r\n"); break; + case '\\': str.Append(L"\\"); break; } } @@ -430,29 +430,31 @@ wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t *str) // turn capitalized characters to small case if (chr < 'a' && chr != '[' && chr != '%') chr = (char)((int)chr + 32); switch (chr) { - case 'c': mir_wstrcat(str, w->cond); break; + case 'c': str.Append(w->cond); break; case 'd': // get the current date GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, nullptr, nullptr, lpzDate, _countof(lpzDate)); - mir_wstrcat(str, lpzDate); break; - case 'e': mir_wstrcat(str, w->dewpoint); break; - case 'f': mir_wstrcat(str, w->feel); break; - case 'h': mir_wstrcat(str, w->high); break; - case 'i': mir_wstrcat(str, w->winddir); break; - case 'l': mir_wstrcat(str, w->low); break; - case 'm': mir_wstrcat(str, w->humid); break; - case 'n': mir_wstrcat(str, w->city); break; - case 'p': mir_wstrcat(str, w->pressure); break; - case 'r': mir_wstrcat(str, w->sunrise); break; - case 's': mir_wstrcat(str, w->id); break; - case 't': mir_wstrcat(str, w->temp); break; + str.Append(lpzDate); break; + case 'e': str.Append(w->dewpoint); break; + case 'f': str.Append(w->feel); break; + case 'h': str.Append(w->high); break; + case 'i': str.Append(w->winddir); break; + case 'l': str.Append(w->low); break; + case 'm': str.Append(w->humid); break; + case 'n': str.Append(w->city); break; + case 'p': str.Append(w->pressure); break; + case 'r': str.Append(w->sunrise); break; + case 's': str.Append(w->id); break; + case 't': str.Append(w->temp); break; case 'u': - if (mir_wstrcmp(w->update, NODATA)) mir_wstrcat(str, w->update); - else mir_wstrcat(str, TranslateT("<unknown time>")); + if (mir_wstrcmp(w->update, NODATA)) + str.Append(w->update); + else + str.Append(TranslateT("<unknown time>")); break; - case 'v': mir_wstrcat(str, w->vis); break; - case 'w': mir_wstrcat(str, w->wind); break; - case 'y': mir_wstrcat(str, w->sunset); break; - case '%': mir_wstrcat(str, L"%"); break; + case 'v': str.Append(w->vis); break; + case 'w': str.Append(w->wind); break; + case 'y': str.Append(w->sunset); break; + case '%': str.Append(L"%"); break; case '[': // custom variables i++; name[0] = 0; @@ -464,17 +466,14 @@ wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t *str) // access the database to get its value if (!db_get_ws(w->hContact, WEATHERCONDITION, name, &dbv)) { if (dbv.pwszVal != TranslateW(NODATA) && dbv.pwszVal != TranslateT("<Error>")) - mir_wstrcat(str, dbv.pwszVal); + str.Append(dbv.pwszVal); db_free(&dbv); } break; } } // if the character is not a variable, write the original character to the new string - else { - mir_snwprintf(lpzDate, L"%c", dis[i]); - mir_wstrcat(str, lpzDate); - } + else str.AppendChar(dis[i]); } return str; diff --git a/protocols/Weather/src/weather_data.cpp b/protocols/Weather/src/weather_data.cpp index a02ae0dc1f..76e50ca139 100644 --- a/protocols/Weather/src/weather_data.cpp +++ b/protocols/Weather/src/weather_data.cpp @@ -26,18 +26,6 @@ saving individual weather data for a weather contact. #include "stdafx.h" ///////////////////////////////////////////////////////////////////////////////////////// -// get station ID from DB -// hContact = the current contact handle -// return value = the string for station ID - -void CWeatherProto::GetStationID(MCONTACT hContact, wchar_t *id, int idlen) -{ - // accessing the database - if (db_get_wstatic(hContact, m_szModuleName, "ID", id, idlen)) - id[0] = 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// // initialize weather info by loading values from database // hContact = current contact handle // return value = the current weather information in WEATHERINFO struct @@ -49,7 +37,7 @@ WEATHERINFO CWeatherProto::LoadWeatherInfo(MCONTACT hContact) // if the string is not found in database, a value of "N/A" is stored in the field WEATHERINFO winfo; winfo.hContact = hContact; - GetStationID(hContact, winfo.id, _countof(winfo.id)); + wcsncpy_s(winfo.id, getMStringW(hContact, "ID"), _countof(winfo.id)); if (db_get_wstatic(hContact, m_szModuleName, "Nick", winfo.city, _countof(winfo.city))) wcsncpy(winfo.city, NODATA, _countof(winfo.city) - 1); @@ -121,7 +109,7 @@ void CWeatherProto::EraseAllInfo() // if no default station find, assign a new one if (opt.Default[0] == 0) { - GetStationID(hContact, opt.Default, _countof(opt.Default)); + wcsncpy_s(opt.Default, getMStringW(hContact, "ID"), _countof(opt.Default)); opt.DefStn = hContact; if (!getWString(hContact, "Nick", &dbv)) { diff --git a/protocols/Weather/src/weather_mwin.cpp b/protocols/Weather/src/weather_mwin.cpp index 57b775c81b..058c6e56ad 100644 --- a/protocols/Weather/src/weather_mwin.cpp +++ b/protocols/Weather/src/weather_mwin.cpp @@ -340,7 +340,7 @@ void CWeatherProto::InitMwin(void) g_plugin.addFont(&fontid); for (auto &hContact : AccContacts()) - if (g_plugin.getDword(hContact, "mwin")) + if (getDword(hContact, "mwin")) AddFrameWindow(hContact); hFontHook = HookEvent(ME_FONT_RELOAD, RedrawFrame); @@ -349,7 +349,7 @@ void CWeatherProto::InitMwin(void) void CWeatherProto::DestroyMwin(void) { for (auto &hContact : AccContacts()) { - uint32_t frameId = g_plugin.getDword(hContact, "mwin"); + uint32_t frameId = getDword(hContact, "mwin"); if (frameId) CallService(MS_CLIST_FRAMES_REMOVEFRAME, frameId, 0); } diff --git a/protocols/Weather/src/weather_opt.cpp b/protocols/Weather/src/weather_opt.cpp index a0b42e21be..765309e414 100644 --- a/protocols/Weather/src/weather_opt.cpp +++ b/protocols/Weather/src/weather_opt.cpp @@ -295,8 +295,6 @@ public: ///////////////////////////////////////////////////////////////////////////////////////// // text option dialog -#define VAR_LIST_OPT TranslateT("%c\tcurrent condition\n%d\tcurrent date\n%e\tdewpoint\n%f\tfeel-like temp\n%h\ttoday's high\n%i\twind direction\n%l\ttoday's low\n%m\thumidity\n%n\tstation name\n%p\tpressure\n%r\tsunrise time\n%s\tstation ID\n%t\ttemperature\n%u\tupdate time\n%v\tvisibility\n%w\twind speed\n%y\tsun set\n----------\n\\n\tnew line") - struct { wchar_t c; @@ -315,13 +313,40 @@ static controls[] = { 'S', IDC_BTITLE2, "StatusText" }, }; +struct +{ + wchar_t symbol; + const wchar_t *pwszText; +} +static variables[] = +{ + { 'c', LPGENW("Current condition") }, + { 'd', LPGENW("Current date") }, + { 'e', LPGENW("Dewpoint") }, + { 'f', LPGENW("Feel-like temp") }, + { 'h', LPGENW("Today's high") }, + { 'i', LPGENW("Wind direction") }, + { 'l', LPGENW("Today's low") }, + { 'm', LPGENW("Humidity") }, + { 'n', LPGENW("Station name") }, + { 'p', LPGENW("Pressure") }, + { 'r', LPGENW("Sunrise") }, + { 's', LPGENW("Station ID") }, + { 't', LPGENW("Temperature") }, + { 'u', LPGENW("Update time") }, + { 'v', LPGENW("Visibility") }, + { 'w', LPGENW("Wind speed") }, + { 'y', LPGENW("Sunset") }, +}; + class COptionsTextDlg : public CWeatherDlgBase { - CCtrlMButton btnReset, tm1, tm2, tm3, tm4, tm5, tm6, tm7, tm8; + CCtrlMButton btnMore, btnReset, tm1, tm2, tm3, tm4, tm5, tm6, tm7, tm8; public: COptionsTextDlg(CWeatherProto *ppro) : CWeatherDlgBase(ppro, IDD_TEXTOPT), + btnMore(this, IDC_MORE), btnReset(this, IDC_RESET), tm1(this, IDC_TM1), tm2(this, IDC_TM2), @@ -332,6 +357,7 @@ public: tm7(this, IDC_TM7), tm8(this, IDC_TM8) { + btnMore.OnClick = Callback(this, &COptionsTextDlg::onClick_More); btnReset.OnClick = Callback(this, &COptionsTextDlg::onClick_Reset); tm1.OnClick = tm2.OnClick = tm3.OnClick = tm4.OnClick = tm5.OnClick = tm6.OnClick = tm7.OnClick = tm8.OnClick = @@ -346,7 +372,12 @@ public: SetWindowPos(m_hwnd, HWND_TOPMOST, rc.left, rc.top, 0, 0, SWP_NOSIZE); // generate the display text for variable list - SetDlgItemTextW(m_hwnd, IDC_VARLIST, VAR_LIST_OPT); + CMStringW str; + for (auto &it : variables) + str.AppendFormat(L"%%%c\t%s\r\n", it.symbol, TranslateW(it.pwszText)); + str.Append(L"----------\r\n"); + str.AppendFormat(L"\\n\t%s\r\n", TranslateT("new line")); + SetDlgItemTextW(m_hwnd, IDC_VARLIST, str); for (auto &it : controls) SetDlgItemTextW(m_hwnd, it.id, m_proto->GetTextValue(it.c)); @@ -360,6 +391,7 @@ public: tm6.MakeFlat(); tm7.MakeFlat(); tm8.MakeFlat(); + btnMore.MakeFlat(); btnReset.MakeFlat(); return true; } @@ -367,8 +399,8 @@ public: bool OnApply() override { // save the option - wchar_t textstr[MAX_TEXT_SIZE]; for (auto &it : controls) { + wchar_t textstr[MAX_TEXT_SIZE]; GetDlgItemText(m_hwnd, it.id, textstr, _countof(textstr)); if (!mir_wstrcmpi(textstr, GetDefaultText(it.c))) m_proto->delSetting(it.setting); @@ -381,45 +413,55 @@ public: return true; } + void onClick_More(CCtrlButton *) + { + // heading + CMStringW str(TranslateT("Here is a list of custom variables that are currently available")); + str += L"\n\n"; + m_proto->GetVarsDescr(str); + + // display the list in a message box + MessageBox(nullptr, str, TranslateT("More Variables"), MB_OK | MB_ICONINFORMATION | MB_TOPMOST); + } + void onClick_TM(CCtrlButton *pButton) { // display the menu - RECT pos; - GetWindowRect(pButton->GetHwnd(), &pos); HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_TMMENU)); HMENU hMenu1 = GetSubMenu(hMenu, 0); TranslateMenu(hMenu1); - { - auto &var = controls[pButton->GetCtrlId() - IDC_TM1]; - - switch (TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, m_hwnd, nullptr)) { - case ID_MPREVIEW: - { - // show the preview in a message box, using the weather data from the default station - WEATHERINFO winfo = m_proto->LoadWeatherInfo(m_proto->opt.DefStn); - wchar_t buf[MAX_TEXT_SIZE], str[4096]; - GetDlgItemTextW(m_hwnd, var.id, buf, _countof(buf)); - GetDisplay(&winfo, buf, str); - MessageBox(nullptr, str, TranslateT("Weather Protocol Text Preview"), MB_OK | MB_TOPMOST); - } - break; - - case ID_MRESET: - SetDlgItemTextW(m_hwnd, var.id, GetDefaultText(var.c)); - break; + + auto &var = controls[pButton->GetCtrlId() - IDC_TM1]; + + RECT pos; + GetWindowRect(pButton->GetHwnd(), &pos); + switch (TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, m_hwnd, nullptr)) { + case ID_MPREVIEW: + { + // show the preview in a message box, using the weather data from the default station + WEATHERINFO winfo = m_proto->LoadWeatherInfo(m_proto->opt.DefStn); + wchar_t buf[MAX_TEXT_SIZE]; + GetDlgItemTextW(m_hwnd, var.id, buf, _countof(buf)); + MessageBox(nullptr, GetDisplay(&winfo, buf), TranslateT("Weather Protocol Text Preview"), MB_OK | MB_TOPMOST); } - DestroyMenu(hMenu); + break; + + case ID_MRESET: + SetDlgItemTextW(m_hwnd, var.id, GetDefaultText(var.c)); + break; } + DestroyMenu(hMenu); } void onClick_Reset(CCtrlButton *) { // left click action selection menu - RECT pos; - GetWindowRect(btnReset.GetHwnd(), &pos); HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_TMENU)); HMENU hMenu1 = GetSubMenu(hMenu, 0); TranslateMenu(hMenu1); + + RECT pos; + GetWindowRect(btnReset.GetHwnd(), &pos); switch (TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, m_hwnd, nullptr)) { case ID_T1: // reset to the strings in memory, discard all changes diff --git a/protocols/Weather/src/weather_popup.cpp b/protocols/Weather/src/weather_popup.cpp index 91dcf51cb9..b9f938e41d 100644 --- a/protocols/Weather/src/weather_popup.cpp +++ b/protocols/Weather/src/weather_popup.cpp @@ -120,8 +120,8 @@ int CWeatherProto::WeatherPopup(MCONTACT hContact, bool bAlways) POPUPDATAW ppd; ppd.lchContact = hContact; ppd.PluginData = ppd.lchIcon = GetStatusIcon(winfo.hContact); - GetDisplay(&winfo, GetTextValue('P'), ppd.lpwzContactName); - GetDisplay(&winfo, GetTextValue('p'), ppd.lpwzText); + wcsncpy_s(ppd.lpwzContactName, GetDisplay(&winfo, GetTextValue('P')), _TRUNCATE); + wcsncpy_s(ppd.lpwzText, GetDisplay(&winfo, GetTextValue('p')), _TRUNCATE); ppd.PluginWindowProc = PopupDlgProc; ppd.colorBack = (opt.UseWinColors) ? GetSysColor(COLOR_BTNFACE) : opt.BGColour; ppd.colorText = (opt.UseWinColors) ? GetSysColor(COLOR_WINDOWTEXT) : opt.TextColour; diff --git a/protocols/Weather/src/weather_proto.cpp b/protocols/Weather/src/weather_proto.cpp index 6df768e7ac..3c5f8f1057 100644 --- a/protocols/Weather/src/weather_proto.cpp +++ b/protocols/Weather/src/weather_proto.cpp @@ -32,8 +32,6 @@ CWeatherProto::CWeatherProto(const char *protoName, const wchar_t *userName) : HookProtoEvent(ME_CLIST_DOUBLECLICKED, &CWeatherProto::BriefInfoEvt); HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CWeatherProto::BuildContactMenu); - InitMwin(); - // load options and set defaults LoadOptions(); @@ -63,6 +61,8 @@ CWeatherProto::~CWeatherProto() void CWeatherProto::OnModulesLoaded() { + InitMwin(); + // timer for the first update m_impl.m_start.Start(5000); // first update is 5 sec after load diff --git a/protocols/Weather/src/weather_update.cpp b/protocols/Weather/src/weather_update.cpp index 15713e110e..82437dbb72 100644 --- a/protocols/Weather/src/weather_update.cpp +++ b/protocols/Weather/src/weather_update.cpp @@ -32,7 +32,6 @@ menu items). int CWeatherProto::UpdateWeather(MCONTACT hContact) { - wchar_t str2[MAX_TEXT_SIZE]; DBVARIANT dbv; BOOL Ch = FALSE; @@ -128,22 +127,18 @@ int CWeatherProto::UpdateWeather(MCONTACT hContact) setWord(hContact, "Status", iStatus); AvatarDownloaded(hContact); - GetDisplay(&winfo, GetTextValue('C'), str2); - db_set_ws(hContact, "CList", "MyHandle", str2); + db_set_ws(hContact, "CList", "MyHandle", GetDisplay(&winfo, GetTextValue('C'))); - GetDisplay(&winfo, GetTextValue('S'), str2); - if (str2[0]) + CMStringW str2(GetDisplay(&winfo, GetTextValue('S'))); + if (!str2.IsEmpty()) db_set_ws(hContact, "CList", "StatusMsg", str2); else db_unset(hContact, "CList", "StatusMsg"); - - ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, nullptr, (LPARAM)(str2[0] ? str2 : nullptr)); + ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, nullptr, (LPARAM)(str2.IsEmpty() ? nullptr : str2.c_str())); // save descriptions in MyNotes - GetDisplay(&winfo, GetTextValue('N'), str2); - db_set_ws(hContact, "UserInfo", "MyNotes", str2); - GetDisplay(&winfo, GetTextValue('X'), str2); - db_set_ws(hContact, WEATHERCONDITION, "WeatherInfo", str2); + db_set_ws(hContact, "UserInfo", "MyNotes", GetDisplay(&winfo, GetTextValue('N'))); + db_set_ws(hContact, WEATHERCONDITION, "WeatherInfo", GetDisplay(&winfo, GetTextValue('X'))); // set the update tag setByte(hContact, "IsUpdated", TRUE); @@ -174,8 +169,7 @@ int CWeatherProto::UpdateWeather(MCONTACT hContact) db_free(&dbv); if (file != nullptr) { // write data to the file and close - GetDisplay(&winfo, GetTextValue('E'), str2); - fputws(str2, file); + fputws(GetDisplay(&winfo, GetTextValue('E')), file); fclose(file); } } @@ -183,9 +177,7 @@ int CWeatherProto::UpdateWeather(MCONTACT hContact) if (getByte(hContact, "History")) { // internal log using history - GetDisplay(&winfo, GetTextValue('H'), str2); - - T2Utf szMessage(str2); + T2Utf szMessage(GetDisplay(&winfo, GetTextValue('H'))); DBEVENTINFO dbei = {}; dbei.szModule = m_szModuleName; @@ -381,6 +373,7 @@ static double g_elevation = 0; static void getData(OBJLIST<WIDATAITEM> &arValues, const JSONNode &node) { + arValues.insert(new WIDATAITEM(LPGENW("Date"), L"", parseConditions(node["datetime"].as_mstring()))); arValues.insert(new WIDATAITEM(LPGENW("Condition"), L"", parseConditions(node["conditions"].as_mstring()))); arValues.insert(new WIDATAITEM(LPGENW("Temperature"), L"C", node["temp"].as_mstring())); arValues.insert(new WIDATAITEM(LPGENW("High"), L"C", node["tempmax"].as_mstring())); @@ -403,15 +396,14 @@ static void getData(OBJLIST<WIDATAITEM> &arValues, const JSONNode &node) int CWeatherProto::GetWeatherData(MCONTACT hContact) { // get each part of the id's - wchar_t id[256]; - GetStationID(hContact, id, _countof(id)); - if (id[0] == 0) + CMStringW wszID(getMStringW(hContact, "ID")); + if (wszID.IsEmpty()) return INVALID_ID; uint16_t cond = NA; // download the html file from the internet - WeatherReply reply(RunQuery(id, 7)); + WeatherReply reply(RunQuery(wszID, 7)); if (!reply) return reply.error(); @@ -478,6 +470,34 @@ int CWeatherProto::GetWeatherData(MCONTACT hContact) } ///////////////////////////////////////////////////////////////////////////////////////// + +static int enumSettings(const char *pszSetting, void *param) +{ + auto *pList = (OBJLIST<char>*)param; + if (!pList->find((char*)pszSetting)) + pList->insert(newStr(pszSetting)); + return 0; +} + +void CWeatherProto::GetVarsDescr(CMStringW &wszDescr) +{ + OBJLIST<char> vars(10, strcmp); + for (int i = 1; i <= 7; i++) + vars.insert(newStr(CMStringA(FORMAT, "Forecast Day %d", i))); + + for (auto &cc : AccContacts()) + db_enum_settings(cc, &enumSettings, WEATHERCONDITION, &vars); + + CMStringW str; + for (auto &it : vars) { + if (!str.IsEmpty()) + str.Append(L", "); + str.AppendFormat(L"%%[%S]", it); + } + wszDescr += str; +} + +///////////////////////////////////////////////////////////////////////////////////////// // main auto-update timer void CWeatherProto::DoUpdate() diff --git a/protocols/Weather/src/weather_userinfo.cpp b/protocols/Weather/src/weather_userinfo.cpp index 0679ddf6e9..1207d01b4a 100644 --- a/protocols/Weather/src/weather_userinfo.cpp +++ b/protocols/Weather/src/weather_userinfo.cpp @@ -151,8 +151,6 @@ public: { m_list.DeleteAllItems(); - wchar_t str[4096]; - // load weather information from the contact into the WEATHERINFO struct WEATHERINFO winfo = m_proto->LoadWeatherInfo(hContact); // check if data exist. If not, display error message box @@ -160,13 +158,11 @@ public: SetDlgItemTextW(m_hwnd, IDC_MTEXT, TranslateT("No information available.\r\nPlease update weather condition first.")); else { // set the display text and show the message box - GetDisplay(&winfo, m_proto->GetTextValue('B'), str); - SetDlgItemTextW(m_hwnd, IDC_MTEXT, str); + SetDlgItemTextW(m_hwnd, IDC_MTEXT, GetDisplay(&winfo, m_proto->GetTextValue('B'))); } - GetDisplay(&winfo, L"%c, %t", str); SetWindowTextW(m_hwnd, winfo.city); - SetDlgItemTextW(m_hwnd, IDC_HEADERBAR, str); + SetDlgItemTextW(m_hwnd, IDC_HEADERBAR, GetDisplay(&winfo, L"%c, %t")); // get all the settings and store them in a temporary list LIST<char> arSettings(10); @@ -311,9 +307,8 @@ public: ppro = (CWeatherProto *)Proto_GetContactInstance(m_hContact); // load weather info for the contact - wchar_t str[MAX_TEXT_SIZE]; WEATHERINFO w = ppro->LoadWeatherInfo(m_hContact); - SetDlgItemText(m_hwnd, IDC_INFO1, GetDisplay(&w, TranslateT("Current condition for %n"), str)); + SetDlgItemText(m_hwnd, IDC_INFO1, GetDisplay(&w, TranslateT("Current condition for %n"))); SendDlgItemMessage(m_hwnd, IDC_INFOICON, STM_SETICON, (WPARAM)ppro->GetStatusIconBig(m_hContact), 0); @@ -327,19 +322,16 @@ public: SendDlgItemMessage(m_hwnd, IDC_INFO2, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), 0); // set the text for displaying other current weather conditions data - GetDisplay(&w, L"%c %t", str); - SetDlgItemText(m_hwnd, IDC_INFO2, str); + SetDlgItemText(m_hwnd, IDC_INFO2, GetDisplay(&w, L"%c %t")); SetDlgItemText(m_hwnd, IDC_INFO3, w.feel); SetDlgItemText(m_hwnd, IDC_INFO4, w.pressure); - GetDisplay(&w, L"%i %w", str); - SetDlgItemText(m_hwnd, IDC_INFO5, str); + SetDlgItemText(m_hwnd, IDC_INFO5, GetDisplay(&w, L"%i %w")); SetDlgItemText(m_hwnd, IDC_INFO6, w.dewpoint); SetDlgItemText(m_hwnd, IDC_INFO7, w.sunrise); SetDlgItemText(m_hwnd, IDC_INFO8, w.sunset); SetDlgItemText(m_hwnd, IDC_INFO9, w.high); SetDlgItemText(m_hwnd, IDC_INFO10, w.low); - GetDisplay(&w, TranslateT("Last update on: %u"), str); - SetDlgItemText(m_hwnd, IDC_INFO11, str); + SetDlgItemText(m_hwnd, IDC_INFO11, GetDisplay(&w, TranslateT("Last update on: %u"))); SetDlgItemText(m_hwnd, IDC_INFO12, w.humid); SetDlgItemText(m_hwnd, IDC_INFO13, w.vis); return true; |
