From de40f3be3f08487937525c2ef096dad665dda61d Mon Sep 17 00:00:00 2001 From: dartraiden Date: Sat, 14 Jan 2023 01:30:59 +0300 Subject: Convert sources to CR+LF --- protocols/Discord/CMakeLists.txt | 8 +- protocols/Discord/discord.vcxproj | 130 +- protocols/Discord/discord.vcxproj.filters | 144 +- protocols/Discord/proto_discord/CMakeLists.txt | 2 +- .../Discord/proto_discord/Proto_Discord.vcxproj | 66 +- .../proto_discord/Proto_Discord.vcxproj.filters | 26 +- .../Discord/proto_discord/res/Proto_Discord.rc | 148 +- protocols/Discord/proto_discord/src/resource.h | 46 +- protocols/Discord/res/discord.rc | 318 +- protocols/Discord/res/version.rc | 18 +- protocols/Discord/src/avatars.cpp | 410 +- protocols/Discord/src/connection.cpp | 246 +- protocols/Discord/src/dispatch.cpp | 1184 +++--- protocols/Discord/src/gateway.cpp | 692 +-- protocols/Discord/src/groupchat.cpp | 470 +- protocols/Discord/src/guilds.cpp | 826 ++-- protocols/Discord/src/http.cpp | 310 +- protocols/Discord/src/main.cpp | 142 +- protocols/Discord/src/menus.cpp | 344 +- protocols/Discord/src/options.cpp | 200 +- protocols/Discord/src/proto.cpp | 1536 +++---- protocols/Discord/src/proto.h | 952 ++--- protocols/Discord/src/resource.h | 60 +- protocols/Discord/src/server.cpp | 614 +-- protocols/Discord/src/stdafx.cxx | 34 +- protocols/Discord/src/stdafx.h | 160 +- protocols/Discord/src/utils.cpp | 752 ++-- protocols/Discord/src/version.h | 26 +- protocols/Discord/src/voice.cpp | 232 +- protocols/Sametime/src/meanwhile/src/mw_error.h | 408 +- protocols/Steam/src/api/enums.h | 176 +- protocols/Steam/src/steam_proto.cpp | 708 +-- protocols/Telegram/.gitignore | 2 +- protocols/Telegram/Telegram.vcxproj | 134 +- protocols/Telegram/Telegram.vcxproj.filters | 118 +- protocols/Telegram/proto_telegram/CMakeLists.txt | 2 +- .../Telegram/proto_telegram/Proto_Telegram.vcxproj | 66 +- .../proto_telegram/Proto_Telegram.vcxproj.filters | 26 +- .../Telegram/proto_telegram/res/Proto_Telegram.rc | 142 +- protocols/Telegram/proto_telegram/src/resource.h | 38 +- protocols/Telegram/res/resource.rc | 308 +- protocols/Telegram/res/telegram.ico | Bin 3638 -> 3640 bytes protocols/Telegram/src/auth.cpp | 252 +- protocols/Telegram/src/avatars.cpp | 190 +- protocols/Telegram/src/main.cpp | 104 +- protocols/Telegram/src/mt_proto.cpp | 396 +- protocols/Telegram/src/mt_proto.h | 418 +- protocols/Telegram/src/options.cpp | 164 +- protocols/Telegram/src/resource.h | 50 +- protocols/Telegram/src/server.cpp | 838 ++-- protocols/Telegram/src/stdafx.cxx | 36 +- protocols/Telegram/src/stdafx.h | 94 +- protocols/Telegram/src/utils.cpp | 252 +- protocols/Telegram/src/utils.h | 6 +- protocols/Telegram/tdlib/tdactor.vcxproj | 148 +- protocols/Telegram/tdlib/tdactor.vcxproj.filters | 118 +- protocols/Telegram/tdlib/tdcore.vcxproj | 596 +-- protocols/Telegram/tdlib/tdcore.vcxproj.filters | 500 +-- protocols/Telegram/tdlib/tddb.vcxproj | 174 +- protocols/Telegram/tdlib/tddb.vcxproj.filters | 78 +- protocols/Telegram/tdlib/tdlib.vcxproj | 1212 +++--- protocols/Telegram/tdlib/tdlib.vcxproj.filters | 3310 +++++++------- protocols/Telegram/tdlib/tdnet.vcxproj | 176 +- protocols/Telegram/tdlib/tdnet.vcxproj.filters | 80 +- protocols/Telegram/tdlib/tdutils.vcxproj | 584 +-- protocols/Telegram/tdlib/tdutils.vcxproj.filters | 508 +-- protocols/WhatsApp/src/appsync.cpp | 644 +-- protocols/WhatsApp/src/chats.cpp | 376 +- protocols/WhatsApp/src/proto.cpp | 616 +-- protocols/WhatsApp/src/proto.h | 1016 ++--- protocols/WhatsApp/src/server.cpp | 836 ++-- protocols/YAMN/YAMN.vcxproj | 192 +- protocols/YAMN/YAMN.vcxproj.filters | 238 +- protocols/YAMN/res/YAMN.rc | 610 +-- protocols/YAMN/src/account.cpp | 2102 ++++----- protocols/YAMN/src/browser/badconnect.cpp | 502 +-- protocols/YAMN/src/browser/browser.h | 76 +- protocols/YAMN/src/browser/mailbrowser.cpp | 4494 ++++++++++---------- protocols/YAMN/src/debug.cpp | 220 +- protocols/YAMN/src/debug.h | 100 +- protocols/YAMN/src/filterplugin.cpp | 338 +- protocols/YAMN/src/mails/decode.cpp | 1068 ++--- protocols/YAMN/src/mails/mails.cpp | 946 ++-- protocols/YAMN/src/mails/mime.cpp | 1390 +++--- protocols/YAMN/src/main.cpp | 648 +-- protocols/YAMN/src/main.h | 74 +- protocols/YAMN/src/proto/netlib.cpp | 464 +- protocols/YAMN/src/proto/pop3/pop3.cpp | 688 +-- protocols/YAMN/src/proto/pop3/pop3comm.cpp | 2658 ++++++------ protocols/YAMN/src/proto/pop3/pop3opt.cpp | 2274 +++++----- protocols/YAMN/src/protoplugin.cpp | 344 +- protocols/YAMN/src/resource.h | 222 +- protocols/YAMN/src/services.cpp | 756 ++-- protocols/YAMN/src/stdafx.h | 506 +-- protocols/YAMN/src/synchro.cpp | 630 +-- protocols/YAMN/src/yamn.cpp | 460 +- 96 files changed, 23998 insertions(+), 23998 deletions(-) (limited to 'protocols') diff --git a/protocols/Discord/CMakeLists.txt b/protocols/Discord/CMakeLists.txt index a227eff6df..d0502167ed 100644 --- a/protocols/Discord/CMakeLists.txt +++ b/protocols/Discord/CMakeLists.txt @@ -1,5 +1,5 @@ -file(GLOB SOURCES "src/*.h" "src/*.cpp" "res/*.rc") -set(TARGET Discord) -include(${CMAKE_SOURCE_DIR}/cmake/plugin.cmake) -target_link_libraries(${TARGET} Zlib libjson) +file(GLOB SOURCES "src/*.h" "src/*.cpp" "res/*.rc") +set(TARGET Discord) +include(${CMAKE_SOURCE_DIR}/cmake/plugin.cmake) +target_link_libraries(${TARGET} Zlib libjson) add_subdirectory(proto_discord) \ No newline at end of file diff --git a/protocols/Discord/discord.vcxproj b/protocols/Discord/discord.vcxproj index ac4c73bd0f..e2f19b6be9 100644 --- a/protocols/Discord/discord.vcxproj +++ b/protocols/Discord/discord.vcxproj @@ -1,66 +1,66 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {88928401-2CE8-4568-AAA7-226141870CBF} - Discord - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - {01F9E227-06F5-4BED-907F-402CA7DFAFE6} - false - - - - - {f6a9340e-b8d9-4c75-be30-47dc66d0abc7} - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {88928401-2CE8-4568-AAA7-226141870CBF} + Discord + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + {01F9E227-06F5-4BED-907F-402CA7DFAFE6} + false + + + + + {f6a9340e-b8d9-4c75-be30-47dc66d0abc7} + + + + + + \ No newline at end of file diff --git a/protocols/Discord/discord.vcxproj.filters b/protocols/Discord/discord.vcxproj.filters index 18314b26b0..f8b955739b 100644 --- a/protocols/Discord/discord.vcxproj.filters +++ b/protocols/Discord/discord.vcxproj.filters @@ -1,73 +1,73 @@ - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - Resource Files - - + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + \ No newline at end of file diff --git a/protocols/Discord/proto_discord/CMakeLists.txt b/protocols/Discord/proto_discord/CMakeLists.txt index 5ea6891fa1..48da532df6 100644 --- a/protocols/Discord/proto_discord/CMakeLists.txt +++ b/protocols/Discord/proto_discord/CMakeLists.txt @@ -1,2 +1,2 @@ -set(TARGET Proto_Discord) +set(TARGET Proto_Discord) include(${CMAKE_SOURCE_DIR}/cmake/icons.cmake) \ No newline at end of file diff --git a/protocols/Discord/proto_discord/Proto_Discord.vcxproj b/protocols/Discord/proto_discord/Proto_Discord.vcxproj index 8ce8962a22..a17e91b938 100644 --- a/protocols/Discord/proto_discord/Proto_Discord.vcxproj +++ b/protocols/Discord/proto_discord/Proto_Discord.vcxproj @@ -1,34 +1,34 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - Proto_Discord - {6B8BA5EE-3815-44A6-A13B-2A22E8B3A311} - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Proto_Discord + {6B8BA5EE-3815-44A6-A13B-2A22E8B3A311} + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Discord/proto_discord/Proto_Discord.vcxproj.filters b/protocols/Discord/proto_discord/Proto_Discord.vcxproj.filters index 3f512b9b20..a86aceb510 100644 --- a/protocols/Discord/proto_discord/Proto_Discord.vcxproj.filters +++ b/protocols/Discord/proto_discord/Proto_Discord.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - - Header Files - - - - - Resource Files - - + + + + + + Header Files + + + + + Resource Files + + \ No newline at end of file diff --git a/protocols/Discord/proto_discord/res/Proto_Discord.rc b/protocols/Discord/proto_discord/res/Proto_Discord.rc index 13d3153e3e..fb320d064b 100644 --- a/protocols/Discord/proto_discord/res/Proto_Discord.rc +++ b/protocols/Discord/proto_discord/res/Proto_Discord.rc @@ -1,74 +1,74 @@ -// 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" -#endif // Russian (Russia) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// 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" +#endif // Russian (Russia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Discord/proto_discord/src/resource.h b/protocols/Discord/proto_discord/src/resource.h index 70e0dd0372..1a283a2809 100644 --- a/protocols/Discord/proto_discord/src/resource.h +++ b/protocols/Discord/proto_discord/src/resource.h @@ -1,23 +1,23 @@ -//{{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 - -// 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 +//{{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 + +// 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/Discord/res/discord.rc b/protocols/Discord/res/discord.rc index 6fac650624..780cae5613 100644 --- a/protocols/Discord/res/discord.rc +++ b/protocols/Discord/res/discord.rc @@ -1,159 +1,159 @@ -// Microsoft Visual C++ generated resource script. -// -#include "..\src\resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "..\\src\\resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.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_MAIN ICON "discord.ico" - -IDI_GROUPCHAT ICON "groupchat.ico" - -IDI_VOICE_CALL ICON "voiceCall.ico" - -IDI_VOICE_ENDED ICON "voiceEnded.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_OPTIONS_ACCOUNT DIALOGEX 0, 0, 305, 144 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - GROUPBOX "User details",IDC_STATIC,7,7,291,46 - LTEXT "E-mail:",IDC_STATIC,17,20,61,8,0,WS_EX_RIGHT - EDITTEXT IDC_USERNAME,84,18,123,13,ES_AUTOHSCROLL - LTEXT "Password:",IDC_STATIC,17,36,61,8,0,WS_EX_RIGHT - EDITTEXT IDC_PASSWORD,84,34,123,13,ES_PASSWORD | ES_AUTOHSCROLL - GROUPBOX "Contacts",IDC_STATIC,7,54,291,86 - LTEXT "Default group:",IDC_STATIC,17,73,61,8,0,WS_EX_RIGHT - EDITTEXT IDC_GROUP,84,71,123,13,ES_AUTOHSCROLL - CONTROL "Enable guilds (servers)",IDC_USEGUILDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,90,275,10 - CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,102,248,10 - CONTROL "Use subgroups for server channels (requires restart)",IDC_USEGROUPS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,114,248,10 - CONTROL "Delete messages in Miranda when they are deleted from server",IDC_DELETE_MSGS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,126,275,10 -END - -IDD_OPTIONS_ACCMGR DIALOGEX 0, 0, 200, 88 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - GROUPBOX "User details",IDC_STATIC,7,7,178,46 - LTEXT "E-mail:",IDC_STATIC,17,20,69,8,0,WS_EX_RIGHT - EDITTEXT IDC_USERNAME,92,18,86,13,ES_AUTOHSCROLL - LTEXT "Password:",IDC_STATIC,17,36,69,8,0,WS_EX_RIGHT - EDITTEXT IDC_PASSWORD,92,34,86,13,ES_PASSWORD | ES_AUTOHSCROLL - GROUPBOX "Contacts",IDC_STATIC,7,56,178,28 - LTEXT "Default group:",IDC_STATIC,17,67,69,8,0,WS_EX_RIGHT - EDITTEXT IDC_GROUP,92,65,86,13,ES_AUTOHSCROLL -END - -IDD_EXTSEARCH DIALOGEX 0, 0, 114, 55 -STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU -EXSTYLE WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - LTEXT "Nick:",IDC_STATIC,6,7,99,8 - EDITTEXT IDC_NICK,3,18,103,12,0,WS_EX_CLIENTEDGE -END - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_OPTIONS_ACCOUNT, DIALOG - BEGIN - END - - IDD_OPTIONS_ACCMGR, DIALOG - BEGIN - END -END -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_OPTIONS_ACCOUNT AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// Microsoft Visual C++ generated resource script. +// +#include "..\src\resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\\src\\resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.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_MAIN ICON "discord.ico" + +IDI_GROUPCHAT ICON "groupchat.ico" + +IDI_VOICE_CALL ICON "voiceCall.ico" + +IDI_VOICE_ENDED ICON "voiceEnded.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS_ACCOUNT DIALOGEX 0, 0, 305, 144 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "User details",IDC_STATIC,7,7,291,46 + LTEXT "E-mail:",IDC_STATIC,17,20,61,8,0,WS_EX_RIGHT + EDITTEXT IDC_USERNAME,84,18,123,13,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,17,36,61,8,0,WS_EX_RIGHT + EDITTEXT IDC_PASSWORD,84,34,123,13,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Contacts",IDC_STATIC,7,54,291,86 + LTEXT "Default group:",IDC_STATIC,17,73,61,8,0,WS_EX_RIGHT + EDITTEXT IDC_GROUP,84,71,123,13,ES_AUTOHSCROLL + CONTROL "Enable guilds (servers)",IDC_USEGUILDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,90,275,10 + CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,102,248,10 + CONTROL "Use subgroups for server channels (requires restart)",IDC_USEGROUPS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,114,248,10 + CONTROL "Delete messages in Miranda when they are deleted from server",IDC_DELETE_MSGS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,126,275,10 +END + +IDD_OPTIONS_ACCMGR DIALOGEX 0, 0, 200, 88 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "User details",IDC_STATIC,7,7,178,46 + LTEXT "E-mail:",IDC_STATIC,17,20,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_USERNAME,92,18,86,13,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,17,36,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_PASSWORD,92,34,86,13,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Contacts",IDC_STATIC,7,56,178,28 + LTEXT "Default group:",IDC_STATIC,17,67,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_GROUP,92,65,86,13,ES_AUTOHSCROLL +END + +IDD_EXTSEARCH DIALOGEX 0, 0, 114, 55 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Nick:",IDC_STATIC,6,7,99,8 + EDITTEXT IDC_NICK,3,18,103,12,0,WS_EX_CLIENTEDGE +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS_ACCOUNT, DIALOG + BEGIN + END + + IDD_OPTIONS_ACCMGR, DIALOG + BEGIN + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS_ACCOUNT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Discord/res/version.rc b/protocols/Discord/res/version.rc index 5a5ddd63ed..bd3c22d943 100644 --- a/protocols/Discord/res/version.rc +++ b/protocols/Discord/res/version.rc @@ -1,9 +1,9 @@ -// Microsoft Visual C++ generated resource script. -// -#ifdef APSTUDIO_INVOKED -#error this file is not editable by Microsoft Visual C++ -#endif //APSTUDIO_INVOKED - -#include "..\src\version.h" - -#include "..\..\build\Version.rc" +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "..\src\version.h" + +#include "..\..\build\Version.rc" diff --git a/protocols/Discord/src/avatars.cpp b/protocols/Discord/src/avatars.cpp index aef0a76e48..fc49a7ec1a 100644 --- a/protocols/Discord/src/avatars.cpp +++ b/protocols/Discord/src/avatars.cpp @@ -1,205 +1,205 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -CMStringW CDiscordProto::GetAvatarFilename(MCONTACT hContact) -{ - CMStringW wszResult(FORMAT, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName); - CreateDirectoryTreeW(wszResult); - - wszResult.AppendChar('\\'); - - const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG)); - wszResult.AppendFormat(L"%lld%s", getId(hContact, DB_KEY_ID), szFileType); - return wszResult; -} - -INT_PTR CDiscordProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) -{ - int res = 0; - - switch (wParam) { - case AF_MAXSIZE: - ((POINT*)lParam)->x = ((POINT*)lParam)->y = 128; - break; - - case AF_FORMATSUPPORTED: - res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG; - break; - - case AF_ENABLED: - case AF_DONTNEEDDELAYS: - case AF_FETCHIFPROTONOTVISIBLE: - case AF_FETCHIFCONTACTOFFLINE: - return 1; - } - - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::OnReceiveAvatar(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq) -{ - PROTO_AVATAR_INFORMATION ai = { 0 }; - ai.format = PA_FORMAT_UNKNOWN; - ai.hContact = (UINT_PTR)pReq->pUserInfo; - - if (reply->resultCode != 200) { -LBL_Error: - ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai); - return; - } - - if (auto *pszHdr = Netlib_GetHeader(reply, "Content-Type")) - ai.format = ProtoGetAvatarFormatByMimeType(pszHdr); - - if (ai.format == PA_FORMAT_UNKNOWN) { - debugLogA("unknown avatar mime type"); - goto LBL_Error; - } - - setByte(ai.hContact, "AvatarType", ai.format); - mir_wstrncpy(ai.filename, GetAvatarFilename(ai.hContact), _countof(ai.filename)); - - FILE *out = _wfopen(ai.filename, L"wb"); - if (out == nullptr) { - debugLogA("cannot open avatar file %S for writing", ai.filename); - goto LBL_Error; - } - - fwrite(reply->pData, 1, reply->dataLength, out); - fclose(out); - - if (ai.hContact) - ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai); - else - ReportSelfAvatarChanged(); -} - -bool CDiscordProto::RetrieveAvatar(MCONTACT hContact) -{ - ptrA szAvatarHash(getStringA(hContact, DB_KEY_AVHASH)); - SnowFlake id = getId(hContact, DB_KEY_ID); - if (id == 0 || szAvatarHash == nullptr) - return false; - - CMStringA szUrl(FORMAT, "https://cdn.discordapp.com/avatars/%lld/%s.jpg", id, szAvatarHash.get()); - AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveAvatar); - pReq->pUserInfo = (void*)hContact; - Push(pReq); - return true; -} - -INT_PTR CDiscordProto::GetAvatarInfo(WPARAM flags, LPARAM lParam) -{ - PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam; - - CMStringW wszFileName(GetAvatarFilename(pai->hContact)); - if (!wszFileName.IsEmpty()) { - mir_wstrncpy(pai->filename, wszFileName, _countof(pai->filename)); - - bool bFileExist = _waccess(wszFileName, 0) == 0; - - // if we still need to load an avatar - if ((flags & GAIF_FORCE) || !bFileExist) { - if (RetrieveAvatar(pai->hContact)) - return GAIR_WAITFOR; - } - else if (bFileExist) - return GAIR_SUCCESS; - } - - return GAIR_NOAVATAR; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::GetMyAvatar(WPARAM wParam, LPARAM lParam) -{ - if (!wParam || !lParam) - return -3; - - wchar_t* buf = (wchar_t*)wParam; - int size = (int)lParam; - - PROTO_AVATAR_INFORMATION ai = {}; - switch (GetAvatarInfo(0, (LPARAM)&ai)) { - case GAIR_SUCCESS: - wcsncpy_s(buf, size, ai.filename, _TRUNCATE); - return 0; - - case GAIR_WAITFOR: - return -1; - } - - return -2; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::SetMyAvatar(WPARAM, LPARAM lParam) -{ - CMStringW wszFileName(GetAvatarFilename(0)); - - const wchar_t *pwszFilename = (const wchar_t*)lParam; - if (pwszFilename == nullptr) { // remove my avatar file - delSetting(DB_KEY_AVHASH); - DeleteFile(wszFileName); - } - - CMStringA szPayload("data:"); - - const char *szMimeType = ProtoGetAvatarMimeType(ProtoGetAvatarFileFormat(pwszFilename)); - if (szMimeType == nullptr) { - debugLogA("invalid file format for avatar %S", pwszFilename); - return 1; - } - szPayload.AppendFormat("%s;base64,", szMimeType); - FILE *in = _wfopen(pwszFilename, L"rb"); - if (in == nullptr) { - debugLogA("cannot open avatar file %S for reading", pwszFilename); - return 2; - } - - int iFileLength = _filelength(_fileno(in)); - ptrA szFileContents((char*)mir_alloc(iFileLength)); - fread(szFileContents, 1, iFileLength, in); - fclose(in); - szPayload.Append(ptrA(mir_base64_encode(szFileContents.get(), iFileLength))); - - JSONNode root; root << CHAR_PARAM("avatar", szPayload); - Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me", nullptr, &root)); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::CheckAvatarChange(MCONTACT hContact, const CMStringW &wszNewHash) -{ - if (wszNewHash.IsEmpty()) - return; - - ptrW wszOldAvatar(getWStringA(hContact, DB_KEY_AVHASH)); - - // if avatar's hash changed, we need to request a new one - if (mir_wstrcmp(wszNewHash, wszOldAvatar)) { - setWString(hContact, DB_KEY_AVHASH, wszNewHash); - RetrieveAvatar(hContact); - } -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +CMStringW CDiscordProto::GetAvatarFilename(MCONTACT hContact) +{ + CMStringW wszResult(FORMAT, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName); + CreateDirectoryTreeW(wszResult); + + wszResult.AppendChar('\\'); + + const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG)); + wszResult.AppendFormat(L"%lld%s", getId(hContact, DB_KEY_ID), szFileType); + return wszResult; +} + +INT_PTR CDiscordProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) +{ + int res = 0; + + switch (wParam) { + case AF_MAXSIZE: + ((POINT*)lParam)->x = ((POINT*)lParam)->y = 128; + break; + + case AF_FORMATSUPPORTED: + res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG; + break; + + case AF_ENABLED: + case AF_DONTNEEDDELAYS: + case AF_FETCHIFPROTONOTVISIBLE: + case AF_FETCHIFCONTACTOFFLINE: + return 1; + } + + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnReceiveAvatar(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq) +{ + PROTO_AVATAR_INFORMATION ai = { 0 }; + ai.format = PA_FORMAT_UNKNOWN; + ai.hContact = (UINT_PTR)pReq->pUserInfo; + + if (reply->resultCode != 200) { +LBL_Error: + ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai); + return; + } + + if (auto *pszHdr = Netlib_GetHeader(reply, "Content-Type")) + ai.format = ProtoGetAvatarFormatByMimeType(pszHdr); + + if (ai.format == PA_FORMAT_UNKNOWN) { + debugLogA("unknown avatar mime type"); + goto LBL_Error; + } + + setByte(ai.hContact, "AvatarType", ai.format); + mir_wstrncpy(ai.filename, GetAvatarFilename(ai.hContact), _countof(ai.filename)); + + FILE *out = _wfopen(ai.filename, L"wb"); + if (out == nullptr) { + debugLogA("cannot open avatar file %S for writing", ai.filename); + goto LBL_Error; + } + + fwrite(reply->pData, 1, reply->dataLength, out); + fclose(out); + + if (ai.hContact) + ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai); + else + ReportSelfAvatarChanged(); +} + +bool CDiscordProto::RetrieveAvatar(MCONTACT hContact) +{ + ptrA szAvatarHash(getStringA(hContact, DB_KEY_AVHASH)); + SnowFlake id = getId(hContact, DB_KEY_ID); + if (id == 0 || szAvatarHash == nullptr) + return false; + + CMStringA szUrl(FORMAT, "https://cdn.discordapp.com/avatars/%lld/%s.jpg", id, szAvatarHash.get()); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveAvatar); + pReq->pUserInfo = (void*)hContact; + Push(pReq); + return true; +} + +INT_PTR CDiscordProto::GetAvatarInfo(WPARAM flags, LPARAM lParam) +{ + PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam; + + CMStringW wszFileName(GetAvatarFilename(pai->hContact)); + if (!wszFileName.IsEmpty()) { + mir_wstrncpy(pai->filename, wszFileName, _countof(pai->filename)); + + bool bFileExist = _waccess(wszFileName, 0) == 0; + + // if we still need to load an avatar + if ((flags & GAIF_FORCE) || !bFileExist) { + if (RetrieveAvatar(pai->hContact)) + return GAIR_WAITFOR; + } + else if (bFileExist) + return GAIR_SUCCESS; + } + + return GAIR_NOAVATAR; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::GetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + if (!wParam || !lParam) + return -3; + + wchar_t* buf = (wchar_t*)wParam; + int size = (int)lParam; + + PROTO_AVATAR_INFORMATION ai = {}; + switch (GetAvatarInfo(0, (LPARAM)&ai)) { + case GAIR_SUCCESS: + wcsncpy_s(buf, size, ai.filename, _TRUNCATE); + return 0; + + case GAIR_WAITFOR: + return -1; + } + + return -2; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::SetMyAvatar(WPARAM, LPARAM lParam) +{ + CMStringW wszFileName(GetAvatarFilename(0)); + + const wchar_t *pwszFilename = (const wchar_t*)lParam; + if (pwszFilename == nullptr) { // remove my avatar file + delSetting(DB_KEY_AVHASH); + DeleteFile(wszFileName); + } + + CMStringA szPayload("data:"); + + const char *szMimeType = ProtoGetAvatarMimeType(ProtoGetAvatarFileFormat(pwszFilename)); + if (szMimeType == nullptr) { + debugLogA("invalid file format for avatar %S", pwszFilename); + return 1; + } + szPayload.AppendFormat("%s;base64,", szMimeType); + FILE *in = _wfopen(pwszFilename, L"rb"); + if (in == nullptr) { + debugLogA("cannot open avatar file %S for reading", pwszFilename); + return 2; + } + + int iFileLength = _filelength(_fileno(in)); + ptrA szFileContents((char*)mir_alloc(iFileLength)); + fread(szFileContents, 1, iFileLength, in); + fclose(in); + szPayload.Append(ptrA(mir_base64_encode(szFileContents.get(), iFileLength))); + + JSONNode root; root << CHAR_PARAM("avatar", szPayload); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me", nullptr, &root)); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::CheckAvatarChange(MCONTACT hContact, const CMStringW &wszNewHash) +{ + if (wszNewHash.IsEmpty()) + return; + + ptrW wszOldAvatar(getWStringA(hContact, DB_KEY_AVHASH)); + + // if avatar's hash changed, we need to request a new one + if (mir_wstrcmp(wszNewHash, wszOldAvatar)) { + setWString(hContact, DB_KEY_AVHASH, wszNewHash); + RetrieveAvatar(hContact); + } +} diff --git a/protocols/Discord/src/connection.cpp b/protocols/Discord/src/connection.cpp index a85d5738a0..d98d6e4ec8 100644 --- a/protocols/Discord/src/connection.cpp +++ b/protocols/Discord/src/connection.cpp @@ -1,123 +1,123 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq) -{ - CMStringA str; - - pReq->szUrl = pReq->m_szUrl.GetBuffer(); - if (!pReq->m_szParam.IsEmpty()) { - if (pReq->requestType == REQUEST_GET) { - str.Format("%s?%s", pReq->m_szUrl.c_str(), pReq->m_szParam.c_str()); - pReq->szUrl = str.GetBuffer(); - } - else { - pReq->pData = mir_strdup(pReq->m_szParam); - pReq->dataLength = pReq->m_szParam.GetLength(); - } - } - - if (pReq->m_bMainSite) { - pReq->flags |= NLHRF_PERSISTENT; - pReq->nlc = m_hAPIConnection; - pReq->AddHeader("Cookie", m_szCookie); - } - - bool bRetryable = pReq->nlc != nullptr; - debugLogA("Executing request #%d:\n%s", pReq->m_iReqNum, pReq->szUrl); - -LBL_Retry: - NLHR_PTR reply(Netlib_HttpTransaction(m_hNetlibUser, pReq)); - if (reply == nullptr) { - debugLogA("Request %d failed", pReq->m_iReqNum); - - if (pReq->m_bMainSite) { - if (IsStatusConnecting(m_iStatus)) - ConnectionFailed(LOGINERR_NONETWORK); - m_hAPIConnection = nullptr; - } - - if (bRetryable) { - debugLogA("Attempt to retry request #%d", pReq->m_iReqNum); - pReq->nlc = nullptr; - bRetryable = false; - goto LBL_Retry; - } - } - else { - if (pReq->m_pFunc != nullptr) - (this->*(pReq->m_pFunc))(reply, pReq); - - if (pReq->m_bMainSite) - m_hAPIConnection = reply->nlc; - } - delete pReq; -} - -void CDiscordProto::OnLoggedIn() -{ - debugLogA("CDiscordProto::OnLoggedIn"); - m_bOnline = true; - SetServerStatus(m_iDesiredStatus); -} - -void CDiscordProto::OnLoggedOut() -{ - debugLogA("CDiscordProto::OnLoggedOut"); - m_bOnline = false; - m_bTerminated = true; - m_iGatewaySeq = 0; - m_szTempToken = nullptr; - m_szCookie.Empty(); - m_szWSCookie.Empty(); - - m_impl.m_heartBeat.StopSafe(); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - setAllContactStatuses(ID_STATUS_OFFLINE, false); -} - -void CDiscordProto::ShutdownSession() -{ - if (m_bTerminated) - return; - - debugLogA("CDiscordProto::ShutdownSession"); - - // shutdown all resources - if (m_hWorkerThread) - SetEvent(m_evRequestsQueue); - if (m_hGatewayConnection) - Netlib_Shutdown(m_hGatewayConnection); - if (m_hAPIConnection) - Netlib_Shutdown(m_hAPIConnection); - - OnLoggedOut(); -} - -void CDiscordProto::ConnectionFailed(int iReason) -{ - debugLogA("CDiscordProto::ConnectionFailed -> reason %d", iReason); - delSetting("AccessToken"); - - ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, iReason); - ShutdownSession(); -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq) +{ + CMStringA str; + + pReq->szUrl = pReq->m_szUrl.GetBuffer(); + if (!pReq->m_szParam.IsEmpty()) { + if (pReq->requestType == REQUEST_GET) { + str.Format("%s?%s", pReq->m_szUrl.c_str(), pReq->m_szParam.c_str()); + pReq->szUrl = str.GetBuffer(); + } + else { + pReq->pData = mir_strdup(pReq->m_szParam); + pReq->dataLength = pReq->m_szParam.GetLength(); + } + } + + if (pReq->m_bMainSite) { + pReq->flags |= NLHRF_PERSISTENT; + pReq->nlc = m_hAPIConnection; + pReq->AddHeader("Cookie", m_szCookie); + } + + bool bRetryable = pReq->nlc != nullptr; + debugLogA("Executing request #%d:\n%s", pReq->m_iReqNum, pReq->szUrl); + +LBL_Retry: + NLHR_PTR reply(Netlib_HttpTransaction(m_hNetlibUser, pReq)); + if (reply == nullptr) { + debugLogA("Request %d failed", pReq->m_iReqNum); + + if (pReq->m_bMainSite) { + if (IsStatusConnecting(m_iStatus)) + ConnectionFailed(LOGINERR_NONETWORK); + m_hAPIConnection = nullptr; + } + + if (bRetryable) { + debugLogA("Attempt to retry request #%d", pReq->m_iReqNum); + pReq->nlc = nullptr; + bRetryable = false; + goto LBL_Retry; + } + } + else { + if (pReq->m_pFunc != nullptr) + (this->*(pReq->m_pFunc))(reply, pReq); + + if (pReq->m_bMainSite) + m_hAPIConnection = reply->nlc; + } + delete pReq; +} + +void CDiscordProto::OnLoggedIn() +{ + debugLogA("CDiscordProto::OnLoggedIn"); + m_bOnline = true; + SetServerStatus(m_iDesiredStatus); +} + +void CDiscordProto::OnLoggedOut() +{ + debugLogA("CDiscordProto::OnLoggedOut"); + m_bOnline = false; + m_bTerminated = true; + m_iGatewaySeq = 0; + m_szTempToken = nullptr; + m_szCookie.Empty(); + m_szWSCookie.Empty(); + + m_impl.m_heartBeat.StopSafe(); + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void CDiscordProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("CDiscordProto::ShutdownSession"); + + // shutdown all resources + if (m_hWorkerThread) + SetEvent(m_evRequestsQueue); + if (m_hGatewayConnection) + Netlib_Shutdown(m_hGatewayConnection); + if (m_hAPIConnection) + Netlib_Shutdown(m_hAPIConnection); + + OnLoggedOut(); +} + +void CDiscordProto::ConnectionFailed(int iReason) +{ + debugLogA("CDiscordProto::ConnectionFailed -> reason %d", iReason); + delSetting("AccessToken"); + + ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, iReason); + ShutdownSession(); +} diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp index 5d79feb9fe..7554fa669c 100644 --- a/protocols/Discord/src/dispatch.cpp +++ b/protocols/Discord/src/dispatch.cpp @@ -1,592 +1,592 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -#pragma pack(4) - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CDiscordCommand -{ - const wchar_t *szCommandId; - GatewayHandlerFunc pFunc; -} -static handlers[] = // these structures must me sorted alphabetically -{ - { L"CALL_CREATE", &CDiscordProto::OnCommandCallCreated }, - { L"CALL_DELETE", &CDiscordProto::OnCommandCallDeleted }, - { L"CALL_UPDATE", &CDiscordProto::OnCommandCallUpdated }, - - { L"CHANNEL_CREATE", &CDiscordProto::OnCommandChannelCreated }, - { L"CHANNEL_DELETE", &CDiscordProto::OnCommandChannelDeleted }, - { L"CHANNEL_UPDATE", &CDiscordProto::OnCommandChannelUpdated }, - - { L"GUILD_CREATE", &CDiscordProto::OnCommandGuildCreated }, - { L"GUILD_DELETE", &CDiscordProto::OnCommandGuildDeleted }, - { L"GUILD_MEMBER_ADD", &CDiscordProto::OnCommandGuildMemberAdded }, - { L"GUILD_MEMBER_LIST_UPDATE", &CDiscordProto::OnCommandGuildMemberListUpdate }, - { L"GUILD_MEMBER_REMOVE", &CDiscordProto::OnCommandGuildMemberRemoved }, - { L"GUILD_MEMBER_UPDATE", &CDiscordProto::OnCommandGuildMemberUpdated }, - { L"GUILD_ROLE_CREATE", &CDiscordProto::OnCommandRoleCreated }, - { L"GUILD_ROLE_DELETE", &CDiscordProto::OnCommandRoleDeleted }, - { L"GUILD_ROLE_UPDATE", &CDiscordProto::OnCommandRoleCreated }, - - { L"MESSAGE_ACK", &CDiscordProto::OnCommandMessageAck }, - { L"MESSAGE_CREATE", &CDiscordProto::OnCommandMessageCreate }, - { L"MESSAGE_DELETE", &CDiscordProto::OnCommandMessageDelete }, - { L"MESSAGE_UPDATE", &CDiscordProto::OnCommandMessageUpdate }, - - { L"PRESENCE_UPDATE", &CDiscordProto::OnCommandPresence }, - - { L"READY", &CDiscordProto::OnCommandReady }, - - { L"RELATIONSHIP_ADD", &CDiscordProto::OnCommandFriendAdded }, - { L"RELATIONSHIP_REMOVE", &CDiscordProto::OnCommandFriendRemoved }, - - { L"TYPING_START", &CDiscordProto::OnCommandTyping }, - - { L"USER_SETTINGS_UPDATE", &CDiscordProto::OnCommandUserSettingsUpdate }, - { L"USER_UPDATE", &CDiscordProto::OnCommandUserUpdate }, -}; - -static int __cdecl pSearchFunc(const void *p1, const void *p2) -{ - return wcscmp(((CDiscordCommand*)p1)->szCommandId, ((CDiscordCommand*)p2)->szCommandId); -} - -GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand) -{ - CDiscordCommand tmp = { pwszCommand, nullptr }; - CDiscordCommand *p = (CDiscordCommand*)bsearch(&tmp, handlers, _countof(handlers), sizeof(handlers[0]), pSearchFunc); - return (p != nullptr) ? p->pFunc : nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// channel operations - -void CDiscordProto::OnCommandChannelCreated(const JSONNode &pRoot) -{ - SnowFlake guildId = ::getId(pRoot["guild_id"]); - if (guildId == 0) - PreparePrivateChannel(pRoot); - else { - // group channel for a guild - CDiscordGuild *pGuild = FindGuild(guildId); - if (pGuild && m_bUseGroupchats) { - CDiscordUser *pUser = ProcessGuildChannel(pGuild, pRoot); - if (pUser) - CreateChat(pGuild, pUser); - } - } -} - -void CDiscordProto::OnCommandChannelDeleted(const JSONNode &pRoot) -{ - CDiscordUser *pUser = FindUserByChannel(::getId(pRoot["id"])); - if (pUser == nullptr) - return; - - SnowFlake guildId = ::getId(pRoot["guild_id"]); - if (guildId == 0) { - pUser->channelId = pUser->lastMsgId = 0; - delSetting(pUser->hContact, DB_KEY_CHANNELID); - } - else { - CDiscordGuild *pGuild = FindGuild(guildId); - if (pGuild != nullptr) - Chat_Terminate(m_szModuleName, pUser->wszUsername, true); - } -} - -void CDiscordProto::OnCommandChannelUpdated(const JSONNode &pRoot) -{ - CDiscordUser *pUser = FindUserByChannel(::getId(pRoot["id"])); - if (pUser == nullptr) - return; - - pUser->lastMsgId = ::getId(pRoot["last_message_id"]); - - SnowFlake guildId = ::getId(pRoot["guild_id"]); - if (guildId != 0) { - CDiscordGuild *pGuild = FindGuild(guildId); - if (pGuild == nullptr) - return; - - CMStringW wszName = pRoot["name"].as_mstring(); - if (!wszName.IsEmpty()) { - CMStringW wszNewName = pGuild->wszName + L"#" + wszName; - Chat_ChangeSessionName(m_szModuleName, pUser->wszUsername, wszNewName); - } - - CMStringW wszTopic = pRoot["topic"].as_mstring(); - Chat_SetStatusbarText(m_szModuleName, pUser->wszUsername, wszTopic); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; - gce.pszID.w = pUser->wszUsername; - gce.pszText.w = wszTopic; - gce.time = time(0); - Chat_Event(&gce); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reading a new message - -void CDiscordProto::OnCommandFriendAdded(const JSONNode &pRoot) -{ - CDiscordUser *pUser = PrepareUser(pRoot["user"]); - pUser->bIsPrivate = true; - ProcessType(pUser, pRoot); -} - -void CDiscordProto::OnCommandFriendRemoved(const JSONNode &pRoot) -{ - SnowFlake id = ::getId(pRoot["id"]); - CDiscordUser *pUser = FindUser(id); - if (pUser != nullptr) { - if (pUser->hContact) - if (pUser->bIsPrivate) - db_delete_contact(pUser->hContact); - - arUsers.remove(pUser); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// guild synchronization - -void CDiscordProto::OnCommandGuildCreated(const JSONNode &pRoot) -{ - if (m_bUseGroupchats) - ProcessGuild(pRoot); -} - -void CDiscordProto::OnCommandGuildDeleted(const JSONNode &pRoot) -{ - CDiscordGuild *pGuild = FindGuild(::getId(pRoot["id"])); - if (pGuild == nullptr) - return; - - for (auto &it : arUsers.rev_iter()) - if (it->pGuild == pGuild) { - Chat_Terminate(m_szModuleName, it->wszUsername, true); - arUsers.removeItem(&it); - } - - Chat_Terminate(m_szModuleName, pRoot["name"].as_mstring(), true); - - arGuilds.remove(pGuild); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// guild members - -void CDiscordProto::OnCommandGuildMemberAdded(const JSONNode&) -{ -} - -void CDiscordProto::OnCommandGuildMemberListUpdate(const JSONNode &pRoot) -{ - auto *pGuild = FindGuild(::getId(pRoot["guild_id"])); - if (pGuild == nullptr) - return; - - int iStatus = 0; - - for (auto &ops: pRoot["ops"]) { - for (auto &it : ops["items"]) { - auto &item = it.at((size_t)0); - if (!mir_strcmp(item .name(), "group")) { - iStatus = item ["id"].as_string() == "online" ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE; - continue; - } - - if (!mir_strcmp(item .name(), "member")) { - bool bNew = false; - auto *pm = ProcessGuildUser(pGuild, item, &bNew); - pm->iStatus = iStatus; - - if (bNew) - AddGuildUser(pGuild, *pm); - else if (iStatus) { - CMStringW wszUserId(FORMAT, L"%lld", pm->userId); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_SETCONTACTSTATUS }; - gce.time = time(0); - gce.pszUID.w = wszUserId; - - for (auto &cc : pGuild->arChannels) { - if (!cc->bIsGroup) - continue; - - gce.pszID.w = cc->wszChannelName; - gce.dwItemData = iStatus; - Chat_Event(&gce); - } - } - } - } - } - - pGuild->bSynced = true; -} - -void CDiscordProto::OnCommandGuildMemberRemoved(const JSONNode &pRoot) -{ - CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); - if (pGuild == nullptr) - return; - - CMStringW wszUserId = pRoot["user"]["id"].as_mstring(); - - for (auto &pUser : arUsers) { - if (pUser->pGuild != pGuild) - continue; - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_PART }; - gce.pszUID.w = pUser->wszUsername; - gce.time = time(0); - gce.pszUID.w = wszUserId; - Chat_Event(&gce); - } -} - -void CDiscordProto::OnCommandGuildMemberUpdated(const JSONNode &pRoot) -{ - CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); - if (pGuild == nullptr) - return; - - CMStringW wszUserId = pRoot["user"]["id"].as_mstring(); - CDiscordGuildMember *gm = pGuild->FindUser(_wtoi64(wszUserId)); - if (gm == nullptr) - return; - - gm->wszDiscordId = pRoot["user"]["username"].as_mstring() + L"#" + pRoot["user"]["discriminator"].as_mstring(); - gm->wszNick = pRoot["nick"].as_mstring(); - if (gm->wszNick.IsEmpty()) - gm->wszNick = pRoot["user"]["username"].as_mstring(); - - for (auto &it : arUsers) { - if (it->pGuild != pGuild) - continue; - - CMStringW wszOldNick; - SESSION_INFO *si = g_chatApi.SM_FindSession(it->wszUsername, m_szModuleName); - if (si != nullptr) { - USERINFO *ui = g_chatApi.UM_FindUser(si, wszUserId); - if (ui != nullptr) - wszOldNick = ui->pszNick; - } - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_NICK }; - gce.pszID.w = it->wszUsername; - gce.time = time(0); - gce.pszUID.w = wszUserId; - gce.pszNick.w = wszOldNick; - gce.pszText.w = gm->wszNick; - Chat_Event(&gce); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// roles - -void CDiscordProto::OnCommandRoleCreated(const JSONNode &pRoot) -{ - CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); - if (pGuild != nullptr) - ProcessRole(pGuild, pRoot["role"]); -} - -void CDiscordProto::OnCommandRoleDeleted(const JSONNode &pRoot) -{ - CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); - if (pGuild == nullptr) - return; - - SnowFlake id = ::getId(pRoot["role_id"]); - CDiscordRole *pRole = pGuild->arRoles.find((CDiscordRole*)&id); - if (pRole == nullptr) - return; - - int iOldPosition = pRole->position; - pGuild->arRoles.remove(pRole); - - for (auto &it : pGuild->arRoles) - if (it->position > iOldPosition) - it->position--; - - for (auto &it : arUsers) { - if (it->pGuild != pGuild) - continue; - - SESSION_INFO *si = g_chatApi.SM_FindSession(it->wszUsername, m_szModuleName); - if (si != nullptr) { - g_chatApi.TM_RemoveAll(&si->pStatuses); - BuildStatusList(pGuild, si); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reading a new message - -void CDiscordProto::OnCommandMessageCreate(const JSONNode &pRoot) -{ - OnCommandMessage(pRoot, true); -} - -void CDiscordProto::OnCommandMessageUpdate(const JSONNode &pRoot) -{ - OnCommandMessage(pRoot, false); -} - -void CDiscordProto::OnCommandMessage(const JSONNode &pRoot, bool bIsNew) -{ - CMStringW wszMessageId = pRoot["id"].as_mstring(); - CMStringW wszUserId = pRoot["author"]["id"].as_mstring(); - SnowFlake userId = _wtoi64(wszUserId); - SnowFlake msgId = _wtoi64(wszMessageId); - - // try to find a sender by his channel - SnowFlake channelId = ::getId(pRoot["channel_id"]); - CDiscordUser *pUser = FindUserByChannel(channelId); - if (pUser == nullptr) { - debugLogA("skipping message with unknown channel id=%lld", channelId); - return; - } - - char szMsgId[100]; - _i64toa_s(msgId, szMsgId, _countof(szMsgId), 10); - - COwnMessage ownMsg(::getId(pRoot["nonce"]), 0); - COwnMessage *p = arOwnMessages.find(&ownMsg); - if (p != nullptr) { // own message? skip it - ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)p->reqId, (LPARAM)szMsgId); - debugLogA("skipping own message with nonce=%lld, id=%lld", ownMsg.nonce, msgId); - } - else { - CMStringW wszText = PrepareMessageText(pRoot); - if (wszText.IsEmpty()) - return; - - // old message? try to restore it from database - bool bOurMessage = userId == m_ownId; - if (!bIsNew) { - MEVENT hOldEvent = db_event_getById(m_szModuleName, szMsgId); - if (hOldEvent) { - DB::EventInfo dbei; - dbei.cbBlob = -1; - if (!db_event_get(hOldEvent, &dbei)) { - ptrW wszOldText(DbEvent_GetTextW(&dbei, CP_UTF8)); - if (wszOldText) - wszText.Insert(0, wszOldText); - if (dbei.flags & DBEF_SENT) - bOurMessage = true; - } - } - } - - const JSONNode &edited = pRoot["edited_timestamp"]; - if (!edited.isnull()) - wszText.AppendFormat(L" (%s %s)", TranslateT("edited at"), edited.as_mstring().c_str()); - - if (pUser->bIsPrivate && !pUser->bIsGroup) { - // if a message has myself as an author, add some flags - PROTORECVEVENT recv = {}; - if (bOurMessage) - recv.flags = PREF_CREATEREAD | PREF_SENT; - - debugLogA("store a message from private user %lld, channel id %lld", pUser->id, pUser->channelId); - ptrA buf(mir_utf8encodeW(wszText)); - - recv.timestamp = (uint32_t)StringToDate(pRoot["timestamp"].as_mstring()); - recv.szMessage = buf; - recv.szMsgId = szMsgId; - ProtoChainRecvMsg(pUser->hContact, &recv); - } - else { - debugLogA("store a message into the group channel id %lld", channelId); - - SESSION_INFO *si = g_chatApi.SM_FindSession(pUser->wszUsername, m_szModuleName); - if (si == nullptr) { - debugLogA("message to unknown channel %lld ignored", channelId); - return; - } - - ProcessChatUser(pUser, wszUserId, pRoot); - - ParseSpecialChars(si, wszText); - wszText.Replace(L"%", L"%%"); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE }; - gce.pszID.w = pUser->wszUsername; - gce.dwFlags = GCEF_ADDTOLOG; - gce.pszUID.w = wszUserId; - gce.pszText.w = wszText; - gce.time = (uint32_t)StringToDate(pRoot["timestamp"].as_mstring()); - gce.bIsMe = bOurMessage; - Chat_Event(&gce); - - debugLogW(L"New channel %s message from %s: %s", si->ptszID, gce.pszUID.w, gce.pszText.w); - } - } - - pUser->lastMsgId = msgId; - - SnowFlake lastId = getId(pUser->hContact, DB_KEY_LASTMSGID); // as stored in a database - if (lastId < msgId) - setId(pUser->hContact, DB_KEY_LASTMSGID, msgId); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// someone changed its status - -void CDiscordProto::OnCommandMessageAck(const JSONNode &pRoot) -{ - CDiscordUser *pUser = FindUserByChannel(pRoot["channel_id"]); - if (pUser != nullptr) - pUser->lastMsgId = ::getId(pRoot["message_id"]); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// message deleted - -void CDiscordProto::OnCommandMessageDelete(const JSONNode &pRoot) -{ - if (!m_bSyncDeleteMsgs) - return; - - CMStringA msgid(pRoot["id"].as_mstring()); - if (!msgid.IsEmpty()) { - MEVENT hEvent = db_event_getById(m_szModuleName, msgid); - if (hEvent) - db_event_delete(hEvent); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// someone changed its status - -void CDiscordProto::OnCommandPresence(const JSONNode &pRoot) -{ - auto *pGuild = FindGuild(::getId(pRoot["user"]["guild_id"])); - if (pGuild == nullptr) - ProcessPresence(pRoot); - // else - // pGuild->ProcessPresence(pRoot); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// gateway session start - -void CDiscordProto::OnCommandReady(const JSONNode &pRoot) -{ - OnLoggedIn(); - - GatewaySendHeartbeat(); - m_impl.m_heartBeat.StartSafe(m_iHartbeatInterval); - - m_szGatewaySessionId = pRoot["session_id"].as_mstring(); - - if (m_bUseGroupchats) - for (auto &it : pRoot["guilds"]) - ProcessGuild(it); - - for (auto &it : pRoot["relationships"]) { - CDiscordUser *pUser = PrepareUser(it["user"]); - ProcessType(pUser, it); - } - - for (auto &it : pRoot["presences"]) - ProcessPresence(it); - - for (auto &it : pRoot["private_channels"]) - PreparePrivateChannel(it); - - for (auto &it : pRoot["read_state"]) { - CDiscordUser *pUser = FindUserByChannel(::getId(it["id"])); - if (pUser != nullptr) - pUser->lastReadId = ::getId(it["last_message_id"]); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// UTN support - -void CDiscordProto::OnCommandTyping(const JSONNode &pRoot) -{ - SnowFlake channelId = ::getId(pRoot["channel_id"]); - debugLogA("user typing notification: channelid=%lld", channelId); - - CDiscordUser *pChannel = FindUserByChannel(channelId); - if (pChannel == nullptr) { - debugLogA("channel with id=%lld is not found", channelId); - return; - } - - // both private groupchats & guild channels are chat rooms for Miranda - if (pChannel->pGuild) { - debugLogA("user is typing in a group channel"); - - CMStringW wszUerId = pRoot["user_id"].as_mstring(); - ProcessGuildUser(pChannel->pGuild, pRoot); // never returns null - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TYPING }; - gce.pszID.w = pChannel->wszUsername; - gce.pszUID.w = wszUerId; - gce.dwItemData = 1; - gce.time = time(0); - Chat_Event(&gce); - } - else { - debugLogA("user is typing in his private channel"); - CallService(MS_PROTO_CONTACTISTYPING, pChannel->hContact, 20); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// User info update - -void CDiscordProto::OnCommandUserUpdate(const JSONNode &pRoot) -{ - SnowFlake id = ::getId(pRoot["id"]); - - MCONTACT hContact; - if (id != m_ownId) { - CDiscordUser *pUser = FindUser(id); - if (pUser == nullptr) - return; - - hContact = pUser->hContact; - } - else hContact = 0; - - // force rereading avatar - CheckAvatarChange(hContact, pRoot["avatar"].as_mstring()); -} - -void CDiscordProto::OnCommandUserSettingsUpdate(const JSONNode &pRoot) -{ - int iStatus = StrToStatus(pRoot["status"].as_mstring()); - if (iStatus != 0) { - int iOldStatus = m_iStatus; m_iStatus = iStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - } -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +#pragma pack(4) + +///////////////////////////////////////////////////////////////////////////////////////// + +struct CDiscordCommand +{ + const wchar_t *szCommandId; + GatewayHandlerFunc pFunc; +} +static handlers[] = // these structures must me sorted alphabetically +{ + { L"CALL_CREATE", &CDiscordProto::OnCommandCallCreated }, + { L"CALL_DELETE", &CDiscordProto::OnCommandCallDeleted }, + { L"CALL_UPDATE", &CDiscordProto::OnCommandCallUpdated }, + + { L"CHANNEL_CREATE", &CDiscordProto::OnCommandChannelCreated }, + { L"CHANNEL_DELETE", &CDiscordProto::OnCommandChannelDeleted }, + { L"CHANNEL_UPDATE", &CDiscordProto::OnCommandChannelUpdated }, + + { L"GUILD_CREATE", &CDiscordProto::OnCommandGuildCreated }, + { L"GUILD_DELETE", &CDiscordProto::OnCommandGuildDeleted }, + { L"GUILD_MEMBER_ADD", &CDiscordProto::OnCommandGuildMemberAdded }, + { L"GUILD_MEMBER_LIST_UPDATE", &CDiscordProto::OnCommandGuildMemberListUpdate }, + { L"GUILD_MEMBER_REMOVE", &CDiscordProto::OnCommandGuildMemberRemoved }, + { L"GUILD_MEMBER_UPDATE", &CDiscordProto::OnCommandGuildMemberUpdated }, + { L"GUILD_ROLE_CREATE", &CDiscordProto::OnCommandRoleCreated }, + { L"GUILD_ROLE_DELETE", &CDiscordProto::OnCommandRoleDeleted }, + { L"GUILD_ROLE_UPDATE", &CDiscordProto::OnCommandRoleCreated }, + + { L"MESSAGE_ACK", &CDiscordProto::OnCommandMessageAck }, + { L"MESSAGE_CREATE", &CDiscordProto::OnCommandMessageCreate }, + { L"MESSAGE_DELETE", &CDiscordProto::OnCommandMessageDelete }, + { L"MESSAGE_UPDATE", &CDiscordProto::OnCommandMessageUpdate }, + + { L"PRESENCE_UPDATE", &CDiscordProto::OnCommandPresence }, + + { L"READY", &CDiscordProto::OnCommandReady }, + + { L"RELATIONSHIP_ADD", &CDiscordProto::OnCommandFriendAdded }, + { L"RELATIONSHIP_REMOVE", &CDiscordProto::OnCommandFriendRemoved }, + + { L"TYPING_START", &CDiscordProto::OnCommandTyping }, + + { L"USER_SETTINGS_UPDATE", &CDiscordProto::OnCommandUserSettingsUpdate }, + { L"USER_UPDATE", &CDiscordProto::OnCommandUserUpdate }, +}; + +static int __cdecl pSearchFunc(const void *p1, const void *p2) +{ + return wcscmp(((CDiscordCommand*)p1)->szCommandId, ((CDiscordCommand*)p2)->szCommandId); +} + +GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand) +{ + CDiscordCommand tmp = { pwszCommand, nullptr }; + CDiscordCommand *p = (CDiscordCommand*)bsearch(&tmp, handlers, _countof(handlers), sizeof(handlers[0]), pSearchFunc); + return (p != nullptr) ? p->pFunc : nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// channel operations + +void CDiscordProto::OnCommandChannelCreated(const JSONNode &pRoot) +{ + SnowFlake guildId = ::getId(pRoot["guild_id"]); + if (guildId == 0) + PreparePrivateChannel(pRoot); + else { + // group channel for a guild + CDiscordGuild *pGuild = FindGuild(guildId); + if (pGuild && m_bUseGroupchats) { + CDiscordUser *pUser = ProcessGuildChannel(pGuild, pRoot); + if (pUser) + CreateChat(pGuild, pUser); + } + } +} + +void CDiscordProto::OnCommandChannelDeleted(const JSONNode &pRoot) +{ + CDiscordUser *pUser = FindUserByChannel(::getId(pRoot["id"])); + if (pUser == nullptr) + return; + + SnowFlake guildId = ::getId(pRoot["guild_id"]); + if (guildId == 0) { + pUser->channelId = pUser->lastMsgId = 0; + delSetting(pUser->hContact, DB_KEY_CHANNELID); + } + else { + CDiscordGuild *pGuild = FindGuild(guildId); + if (pGuild != nullptr) + Chat_Terminate(m_szModuleName, pUser->wszUsername, true); + } +} + +void CDiscordProto::OnCommandChannelUpdated(const JSONNode &pRoot) +{ + CDiscordUser *pUser = FindUserByChannel(::getId(pRoot["id"])); + if (pUser == nullptr) + return; + + pUser->lastMsgId = ::getId(pRoot["last_message_id"]); + + SnowFlake guildId = ::getId(pRoot["guild_id"]); + if (guildId != 0) { + CDiscordGuild *pGuild = FindGuild(guildId); + if (pGuild == nullptr) + return; + + CMStringW wszName = pRoot["name"].as_mstring(); + if (!wszName.IsEmpty()) { + CMStringW wszNewName = pGuild->wszName + L"#" + wszName; + Chat_ChangeSessionName(m_szModuleName, pUser->wszUsername, wszNewName); + } + + CMStringW wszTopic = pRoot["topic"].as_mstring(); + Chat_SetStatusbarText(m_szModuleName, pUser->wszUsername, wszTopic); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; + gce.pszID.w = pUser->wszUsername; + gce.pszText.w = wszTopic; + gce.time = time(0); + Chat_Event(&gce); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reading a new message + +void CDiscordProto::OnCommandFriendAdded(const JSONNode &pRoot) +{ + CDiscordUser *pUser = PrepareUser(pRoot["user"]); + pUser->bIsPrivate = true; + ProcessType(pUser, pRoot); +} + +void CDiscordProto::OnCommandFriendRemoved(const JSONNode &pRoot) +{ + SnowFlake id = ::getId(pRoot["id"]); + CDiscordUser *pUser = FindUser(id); + if (pUser != nullptr) { + if (pUser->hContact) + if (pUser->bIsPrivate) + db_delete_contact(pUser->hContact); + + arUsers.remove(pUser); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// guild synchronization + +void CDiscordProto::OnCommandGuildCreated(const JSONNode &pRoot) +{ + if (m_bUseGroupchats) + ProcessGuild(pRoot); +} + +void CDiscordProto::OnCommandGuildDeleted(const JSONNode &pRoot) +{ + CDiscordGuild *pGuild = FindGuild(::getId(pRoot["id"])); + if (pGuild == nullptr) + return; + + for (auto &it : arUsers.rev_iter()) + if (it->pGuild == pGuild) { + Chat_Terminate(m_szModuleName, it->wszUsername, true); + arUsers.removeItem(&it); + } + + Chat_Terminate(m_szModuleName, pRoot["name"].as_mstring(), true); + + arGuilds.remove(pGuild); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// guild members + +void CDiscordProto::OnCommandGuildMemberAdded(const JSONNode&) +{ +} + +void CDiscordProto::OnCommandGuildMemberListUpdate(const JSONNode &pRoot) +{ + auto *pGuild = FindGuild(::getId(pRoot["guild_id"])); + if (pGuild == nullptr) + return; + + int iStatus = 0; + + for (auto &ops: pRoot["ops"]) { + for (auto &it : ops["items"]) { + auto &item = it.at((size_t)0); + if (!mir_strcmp(item .name(), "group")) { + iStatus = item ["id"].as_string() == "online" ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE; + continue; + } + + if (!mir_strcmp(item .name(), "member")) { + bool bNew = false; + auto *pm = ProcessGuildUser(pGuild, item, &bNew); + pm->iStatus = iStatus; + + if (bNew) + AddGuildUser(pGuild, *pm); + else if (iStatus) { + CMStringW wszUserId(FORMAT, L"%lld", pm->userId); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_SETCONTACTSTATUS }; + gce.time = time(0); + gce.pszUID.w = wszUserId; + + for (auto &cc : pGuild->arChannels) { + if (!cc->bIsGroup) + continue; + + gce.pszID.w = cc->wszChannelName; + gce.dwItemData = iStatus; + Chat_Event(&gce); + } + } + } + } + } + + pGuild->bSynced = true; +} + +void CDiscordProto::OnCommandGuildMemberRemoved(const JSONNode &pRoot) +{ + CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); + if (pGuild == nullptr) + return; + + CMStringW wszUserId = pRoot["user"]["id"].as_mstring(); + + for (auto &pUser : arUsers) { + if (pUser->pGuild != pGuild) + continue; + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_PART }; + gce.pszUID.w = pUser->wszUsername; + gce.time = time(0); + gce.pszUID.w = wszUserId; + Chat_Event(&gce); + } +} + +void CDiscordProto::OnCommandGuildMemberUpdated(const JSONNode &pRoot) +{ + CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); + if (pGuild == nullptr) + return; + + CMStringW wszUserId = pRoot["user"]["id"].as_mstring(); + CDiscordGuildMember *gm = pGuild->FindUser(_wtoi64(wszUserId)); + if (gm == nullptr) + return; + + gm->wszDiscordId = pRoot["user"]["username"].as_mstring() + L"#" + pRoot["user"]["discriminator"].as_mstring(); + gm->wszNick = pRoot["nick"].as_mstring(); + if (gm->wszNick.IsEmpty()) + gm->wszNick = pRoot["user"]["username"].as_mstring(); + + for (auto &it : arUsers) { + if (it->pGuild != pGuild) + continue; + + CMStringW wszOldNick; + SESSION_INFO *si = g_chatApi.SM_FindSession(it->wszUsername, m_szModuleName); + if (si != nullptr) { + USERINFO *ui = g_chatApi.UM_FindUser(si, wszUserId); + if (ui != nullptr) + wszOldNick = ui->pszNick; + } + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_NICK }; + gce.pszID.w = it->wszUsername; + gce.time = time(0); + gce.pszUID.w = wszUserId; + gce.pszNick.w = wszOldNick; + gce.pszText.w = gm->wszNick; + Chat_Event(&gce); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// roles + +void CDiscordProto::OnCommandRoleCreated(const JSONNode &pRoot) +{ + CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); + if (pGuild != nullptr) + ProcessRole(pGuild, pRoot["role"]); +} + +void CDiscordProto::OnCommandRoleDeleted(const JSONNode &pRoot) +{ + CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"])); + if (pGuild == nullptr) + return; + + SnowFlake id = ::getId(pRoot["role_id"]); + CDiscordRole *pRole = pGuild->arRoles.find((CDiscordRole*)&id); + if (pRole == nullptr) + return; + + int iOldPosition = pRole->position; + pGuild->arRoles.remove(pRole); + + for (auto &it : pGuild->arRoles) + if (it->position > iOldPosition) + it->position--; + + for (auto &it : arUsers) { + if (it->pGuild != pGuild) + continue; + + SESSION_INFO *si = g_chatApi.SM_FindSession(it->wszUsername, m_szModuleName); + if (si != nullptr) { + g_chatApi.TM_RemoveAll(&si->pStatuses); + BuildStatusList(pGuild, si); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reading a new message + +void CDiscordProto::OnCommandMessageCreate(const JSONNode &pRoot) +{ + OnCommandMessage(pRoot, true); +} + +void CDiscordProto::OnCommandMessageUpdate(const JSONNode &pRoot) +{ + OnCommandMessage(pRoot, false); +} + +void CDiscordProto::OnCommandMessage(const JSONNode &pRoot, bool bIsNew) +{ + CMStringW wszMessageId = pRoot["id"].as_mstring(); + CMStringW wszUserId = pRoot["author"]["id"].as_mstring(); + SnowFlake userId = _wtoi64(wszUserId); + SnowFlake msgId = _wtoi64(wszMessageId); + + // try to find a sender by his channel + SnowFlake channelId = ::getId(pRoot["channel_id"]); + CDiscordUser *pUser = FindUserByChannel(channelId); + if (pUser == nullptr) { + debugLogA("skipping message with unknown channel id=%lld", channelId); + return; + } + + char szMsgId[100]; + _i64toa_s(msgId, szMsgId, _countof(szMsgId), 10); + + COwnMessage ownMsg(::getId(pRoot["nonce"]), 0); + COwnMessage *p = arOwnMessages.find(&ownMsg); + if (p != nullptr) { // own message? skip it + ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)p->reqId, (LPARAM)szMsgId); + debugLogA("skipping own message with nonce=%lld, id=%lld", ownMsg.nonce, msgId); + } + else { + CMStringW wszText = PrepareMessageText(pRoot); + if (wszText.IsEmpty()) + return; + + // old message? try to restore it from database + bool bOurMessage = userId == m_ownId; + if (!bIsNew) { + MEVENT hOldEvent = db_event_getById(m_szModuleName, szMsgId); + if (hOldEvent) { + DB::EventInfo dbei; + dbei.cbBlob = -1; + if (!db_event_get(hOldEvent, &dbei)) { + ptrW wszOldText(DbEvent_GetTextW(&dbei, CP_UTF8)); + if (wszOldText) + wszText.Insert(0, wszOldText); + if (dbei.flags & DBEF_SENT) + bOurMessage = true; + } + } + } + + const JSONNode &edited = pRoot["edited_timestamp"]; + if (!edited.isnull()) + wszText.AppendFormat(L" (%s %s)", TranslateT("edited at"), edited.as_mstring().c_str()); + + if (pUser->bIsPrivate && !pUser->bIsGroup) { + // if a message has myself as an author, add some flags + PROTORECVEVENT recv = {}; + if (bOurMessage) + recv.flags = PREF_CREATEREAD | PREF_SENT; + + debugLogA("store a message from private user %lld, channel id %lld", pUser->id, pUser->channelId); + ptrA buf(mir_utf8encodeW(wszText)); + + recv.timestamp = (uint32_t)StringToDate(pRoot["timestamp"].as_mstring()); + recv.szMessage = buf; + recv.szMsgId = szMsgId; + ProtoChainRecvMsg(pUser->hContact, &recv); + } + else { + debugLogA("store a message into the group channel id %lld", channelId); + + SESSION_INFO *si = g_chatApi.SM_FindSession(pUser->wszUsername, m_szModuleName); + if (si == nullptr) { + debugLogA("message to unknown channel %lld ignored", channelId); + return; + } + + ProcessChatUser(pUser, wszUserId, pRoot); + + ParseSpecialChars(si, wszText); + wszText.Replace(L"%", L"%%"); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE }; + gce.pszID.w = pUser->wszUsername; + gce.dwFlags = GCEF_ADDTOLOG; + gce.pszUID.w = wszUserId; + gce.pszText.w = wszText; + gce.time = (uint32_t)StringToDate(pRoot["timestamp"].as_mstring()); + gce.bIsMe = bOurMessage; + Chat_Event(&gce); + + debugLogW(L"New channel %s message from %s: %s", si->ptszID, gce.pszUID.w, gce.pszText.w); + } + } + + pUser->lastMsgId = msgId; + + SnowFlake lastId = getId(pUser->hContact, DB_KEY_LASTMSGID); // as stored in a database + if (lastId < msgId) + setId(pUser->hContact, DB_KEY_LASTMSGID, msgId); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// someone changed its status + +void CDiscordProto::OnCommandMessageAck(const JSONNode &pRoot) +{ + CDiscordUser *pUser = FindUserByChannel(pRoot["channel_id"]); + if (pUser != nullptr) + pUser->lastMsgId = ::getId(pRoot["message_id"]); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// message deleted + +void CDiscordProto::OnCommandMessageDelete(const JSONNode &pRoot) +{ + if (!m_bSyncDeleteMsgs) + return; + + CMStringA msgid(pRoot["id"].as_mstring()); + if (!msgid.IsEmpty()) { + MEVENT hEvent = db_event_getById(m_szModuleName, msgid); + if (hEvent) + db_event_delete(hEvent); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// someone changed its status + +void CDiscordProto::OnCommandPresence(const JSONNode &pRoot) +{ + auto *pGuild = FindGuild(::getId(pRoot["user"]["guild_id"])); + if (pGuild == nullptr) + ProcessPresence(pRoot); + // else + // pGuild->ProcessPresence(pRoot); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// gateway session start + +void CDiscordProto::OnCommandReady(const JSONNode &pRoot) +{ + OnLoggedIn(); + + GatewaySendHeartbeat(); + m_impl.m_heartBeat.StartSafe(m_iHartbeatInterval); + + m_szGatewaySessionId = pRoot["session_id"].as_mstring(); + + if (m_bUseGroupchats) + for (auto &it : pRoot["guilds"]) + ProcessGuild(it); + + for (auto &it : pRoot["relationships"]) { + CDiscordUser *pUser = PrepareUser(it["user"]); + ProcessType(pUser, it); + } + + for (auto &it : pRoot["presences"]) + ProcessPresence(it); + + for (auto &it : pRoot["private_channels"]) + PreparePrivateChannel(it); + + for (auto &it : pRoot["read_state"]) { + CDiscordUser *pUser = FindUserByChannel(::getId(it["id"])); + if (pUser != nullptr) + pUser->lastReadId = ::getId(it["last_message_id"]); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// UTN support + +void CDiscordProto::OnCommandTyping(const JSONNode &pRoot) +{ + SnowFlake channelId = ::getId(pRoot["channel_id"]); + debugLogA("user typing notification: channelid=%lld", channelId); + + CDiscordUser *pChannel = FindUserByChannel(channelId); + if (pChannel == nullptr) { + debugLogA("channel with id=%lld is not found", channelId); + return; + } + + // both private groupchats & guild channels are chat rooms for Miranda + if (pChannel->pGuild) { + debugLogA("user is typing in a group channel"); + + CMStringW wszUerId = pRoot["user_id"].as_mstring(); + ProcessGuildUser(pChannel->pGuild, pRoot); // never returns null + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TYPING }; + gce.pszID.w = pChannel->wszUsername; + gce.pszUID.w = wszUerId; + gce.dwItemData = 1; + gce.time = time(0); + Chat_Event(&gce); + } + else { + debugLogA("user is typing in his private channel"); + CallService(MS_PROTO_CONTACTISTYPING, pChannel->hContact, 20); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// User info update + +void CDiscordProto::OnCommandUserUpdate(const JSONNode &pRoot) +{ + SnowFlake id = ::getId(pRoot["id"]); + + MCONTACT hContact; + if (id != m_ownId) { + CDiscordUser *pUser = FindUser(id); + if (pUser == nullptr) + return; + + hContact = pUser->hContact; + } + else hContact = 0; + + // force rereading avatar + CheckAvatarChange(hContact, pRoot["avatar"].as_mstring()); +} + +void CDiscordProto::OnCommandUserSettingsUpdate(const JSONNode &pRoot) +{ + int iStatus = StrToStatus(pRoot["status"].as_mstring()); + if (iStatus != 0) { + int iOldStatus = m_iStatus; m_iStatus = iStatus; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + } +} diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp index 82c3b70eb5..0530945c3e 100644 --- a/protocols/Discord/src/gateway.cpp +++ b/protocols/Discord/src/gateway.cpp @@ -1,346 +1,346 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -////////////////////////////////////////////////////////////////////////////////////// -// sends a piece of JSON to a server via a websocket, masked - -bool CDiscordProto::GatewaySend(const JSONNode &pRoot) -{ - if (m_hGatewayConnection == nullptr) - return false; - - json_string szText = pRoot.write(); - debugLogA("Gateway send: %s", szText.c_str()); - WebSocket_SendText(m_hGatewayConnection, szText.c_str()); - return true; -} - -////////////////////////////////////////////////////////////////////////////////////// -// gateway worker thread - -void CDiscordProto::GatewayThread(void*) -{ - while (GatewayThreadWorker()) - ; - ShutdownSession(); -} - -bool CDiscordProto::GatewayThreadWorker() -{ - NETLIBHTTPHEADER hdrs[] = - { - { "Origin", "https://discord.com" }, - { 0, 0 }, - { 0, 0 }, - }; - - if (!m_szWSCookie.IsEmpty()) { - hdrs[1].szName = "Cookie"; - hdrs[1].szValue = m_szWSCookie.GetBuffer(); - } - - NLHR_PTR pReply(WebSocket_Connect(m_hGatewayNetlibUser, m_szGateway + "/?encoding=json&v=8", hdrs)); - if (pReply == nullptr) { - debugLogA("Gateway connection failed, exiting"); - return false; - } - - if (auto *pszNewCookie = Netlib_GetHeader(pReply, "Set-Cookie")) { - char *p = strchr(pszNewCookie, ';'); - if (p) *p = 0; - - m_szWSCookie = pszNewCookie; - } - - if (pReply->resultCode != 101) { - // if there's no cookie & Miranda is bounced with error 404, simply apply the cookie and try again - if (pReply->resultCode == 404) { - if (hdrs[1].szName == nullptr) - return true; - - m_szWSCookie.Empty(); // don't use the same cookie twice - } - return false; - } - - // succeeded! - debugLogA("Gateway connection succeeded"); - m_hGatewayConnection = pReply->nlc; - - bool bExit = false; - int offset = 0; - MBinBuffer netbuf; - - while (!bExit) { - if (m_bTerminated) - break; - - unsigned char buf[2048]; - int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, MSG_NODUMP); - if (bufSize == 0) { - debugLogA("Gateway connection gracefully closed"); - bExit = !m_bTerminated; - break; - } - if (bufSize < 0) { - debugLogA("Gateway connection error, exiting"); - break; - } - - WSHeader hdr; - if (!WebSocket_InitHeader(hdr, buf, bufSize)) { - offset += bufSize; - continue; - } - offset = 0; - - debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); - - // we have some additional data, not only opcode - if ((size_t)bufSize > hdr.headerSize) { - size_t currPacketSize = bufSize - hdr.headerSize; - netbuf.append(buf, bufSize); - while (currPacketSize < hdr.payloadSize) { - int result = Netlib_Recv(m_hGatewayConnection, (char*)buf, _countof(buf), MSG_NODUMP); - if (result == 0) { - debugLogA("Gateway connection gracefully closed"); - bExit = !m_bTerminated; - break; - } - if (result < 0) { - debugLogA("Gateway connection error, exiting"); - break; - } - currPacketSize += result; - netbuf.append(buf, result); - } - } - - // read all payloads from the current buffer, one by one - size_t prevSize = 0; - while (true) { - switch (hdr.opCode) { - case 0: // text packet - case 1: // binary packet - case 2: // continuation - if (hdr.bIsFinal) { - // process a packet here - CMStringA szJson((char*)netbuf.data() + hdr.headerSize, (int)hdr.payloadSize); - debugLogA("JSON received:\n%s", szJson.c_str()); - JSONNode root = JSONNode::parse(szJson); - if (root) - bExit = GatewayProcess(root); - } - break; - - case 8: // close - debugLogA("server required to exit"); - bExit = true; // simply reconnect, don't exit - break; - - case 9: // ping - debugLogA("ping received"); - Netlib_Send(m_hGatewayConnection, (char*)buf + hdr.headerSize, bufSize - int(hdr.headerSize), 0); - break; - } - - if (hdr.bIsFinal) - netbuf.remove(hdr.headerSize + hdr.payloadSize); - - if (netbuf.length() == 0) - break; - - // if we have not enough data for header, continue reading - if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) - break; - - // if we have not enough data for data, continue reading - if (hdr.headerSize + hdr.payloadSize > netbuf.length()) - break; - - debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); - if (prevSize == netbuf.length()) { - netbuf.remove(prevSize); - debugLogA("dropping current packet, exiting"); - break; - } - - prevSize = netbuf.length(); - } - } - - Netlib_CloseHandle(m_hGatewayConnection); - m_hGatewayConnection = nullptr; - return bExit; -} - -////////////////////////////////////////////////////////////////////////////////////// -// handles server commands - -bool CDiscordProto::GatewayProcess(const JSONNode &pRoot) -{ - int opCode = pRoot["op"].as_int(); - switch (opCode) { - case OPCODE_DISPATCH: // process incoming command - { - int iSeq = pRoot["s"].as_int(); - if (iSeq != 0) - m_iGatewaySeq = iSeq; - - CMStringW wszCommand = pRoot["t"].as_mstring(); - debugLogA("got a server command to dispatch: %S", wszCommand.c_str()); - - GatewayHandlerFunc pFunc = GetHandler(wszCommand); - if (pFunc) - (this->*pFunc)(pRoot["d"]); - } - break; - - case OPCODE_RECONNECT: // we need to reconnect asap - debugLogA("we need to reconnect, leaving worker thread"); - return true; - - case OPCODE_INVALID_SESSION: // session invalidated - if (pRoot["d"].as_bool()) // session can be resumed - GatewaySendResume(); - else { - Sleep(5000); // 5 seconds - recommended timeout - GatewaySendIdentify(); - } - break; - - case OPCODE_HELLO: // hello - m_iHartbeatInterval = pRoot["d"]["heartbeat_interval"].as_int(); - - GatewaySendIdentify(); - break; - - case OPCODE_HEARTBEAT_ACK: // heartbeat ack - break; - - default: - debugLogA("ACHTUNG! Unknown opcode: %d, report it to developer", opCode); - } - - return false; -} - -////////////////////////////////////////////////////////////////////////////////////// -// requests to be sent to a gateway - -void CDiscordProto::GatewaySendGuildInfo(CDiscordGuild *pGuild) -{ - if (!pGuild->arChannels.getCount()) - return; - - JSONNode a1(JSON_ARRAY); a1 << INT_PARAM("", 0) << INT_PARAM("", 99); - - CMStringA szId(FORMAT, "%lld", pGuild->arChannels[0]->id); - JSONNode chl(JSON_ARRAY); chl.set_name(szId.c_str()); chl << a1; - - JSONNode channels; channels.set_name("channels"); channels << chl; - - JSONNode payload; payload.set_name("d"); - payload << SINT64_PARAM("guild_id", pGuild->id) << BOOL_PARAM("typing", true) << BOOL_PARAM("activities", true) << BOOL_PARAM("presences", true) << channels; - - JSONNode root; - root << INT_PARAM("op", OPCODE_REQUEST_SYNC_CHANNEL) << payload; - GatewaySend(root); -} - -void CDiscordProto::GatewaySendHeartbeat() -{ - // we don't send heartbeat packets until we get logged in - if (!m_iHartbeatInterval || !m_iGatewaySeq) - return; - - JSONNode root; - root << INT_PARAM("op", OPCODE_HEARTBEAT) << INT_PARAM("d", m_iGatewaySeq); - GatewaySend(root); -} - -void CDiscordProto::GatewaySendIdentify() -{ - if (m_szAccessToken == nullptr) { - ConnectionFailed(LOGINERR_WRONGPASSWORD); - return; - } - - char szOs[256]; - OS_GetDisplayString(szOs, _countof(szOs)); - - char szVersion[256]; - Miranda_GetVersionText(szVersion, _countof(szVersion)); - - JSONNode props; props.set_name("properties"); - props << CHAR_PARAM("os", szOs) << CHAR_PARAM("browser", "Chrome") << CHAR_PARAM("device", szVersion) - << CHAR_PARAM("referrer", "https://miranda-ng.org") << CHAR_PARAM("referring_domain", "miranda-ng.org"); - - JSONNode payload; payload.set_name("d"); - payload << CHAR_PARAM("token", m_szAccessToken) << props << BOOL_PARAM("compress", false) << INT_PARAM("large_threshold", 250); - - JSONNode root; - root << INT_PARAM("op", OPCODE_IDENTIFY) << payload; - GatewaySend(root); -} - -void CDiscordProto::GatewaySendResume() -{ - char szRandom[40]; - uint8_t random[16]; - Utils_GetRandom(random, _countof(random)); - bin2hex(random, _countof(random), szRandom); - - JSONNode root; - root << CHAR_PARAM("token", szRandom) << CHAR_PARAM("session_id", m_szGatewaySessionId) << INT_PARAM("seq", m_iGatewaySeq); - GatewaySend(root); -} - -bool CDiscordProto::GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText) -{ - if (iStatus == ID_STATUS_OFFLINE) { - Push(new AsyncHttpRequest(this, REQUEST_POST, "/auth/logout", nullptr)); - return true; - } - - const char *pszStatus; - switch (iStatus) { - case ID_STATUS_AWAY: - case ID_STATUS_NA: - pszStatus = "idle"; break; - case ID_STATUS_DND: - pszStatus = "dnd"; break; - case ID_STATUS_INVISIBLE: - pszStatus = "invisible"; break; - default: - pszStatus = "online"; break; - } - - JSONNode payload; payload.set_name("d"); - payload << INT64_PARAM("since", __int64(time(0)) * 1000) << BOOL_PARAM("afk", true) << CHAR_PARAM("status", pszStatus); - if (pwszStatusText == nullptr) - payload << CHAR_PARAM("game", nullptr); - else { - JSONNode game; game.set_name("game"); game << WCHAR_PARAM("name", pwszStatusText) << INT_PARAM("type", 0); - payload << game; - } - - JSONNode root; root << INT_PARAM("op", OPCODE_STATUS_UPDATE) << payload; - return GatewaySend(root); -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +////////////////////////////////////////////////////////////////////////////////////// +// sends a piece of JSON to a server via a websocket, masked + +bool CDiscordProto::GatewaySend(const JSONNode &pRoot) +{ + if (m_hGatewayConnection == nullptr) + return false; + + json_string szText = pRoot.write(); + debugLogA("Gateway send: %s", szText.c_str()); + WebSocket_SendText(m_hGatewayConnection, szText.c_str()); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread + +void CDiscordProto::GatewayThread(void*) +{ + while (GatewayThreadWorker()) + ; + ShutdownSession(); +} + +bool CDiscordProto::GatewayThreadWorker() +{ + NETLIBHTTPHEADER hdrs[] = + { + { "Origin", "https://discord.com" }, + { 0, 0 }, + { 0, 0 }, + }; + + if (!m_szWSCookie.IsEmpty()) { + hdrs[1].szName = "Cookie"; + hdrs[1].szValue = m_szWSCookie.GetBuffer(); + } + + NLHR_PTR pReply(WebSocket_Connect(m_hGatewayNetlibUser, m_szGateway + "/?encoding=json&v=8", hdrs)); + if (pReply == nullptr) { + debugLogA("Gateway connection failed, exiting"); + return false; + } + + if (auto *pszNewCookie = Netlib_GetHeader(pReply, "Set-Cookie")) { + char *p = strchr(pszNewCookie, ';'); + if (p) *p = 0; + + m_szWSCookie = pszNewCookie; + } + + if (pReply->resultCode != 101) { + // if there's no cookie & Miranda is bounced with error 404, simply apply the cookie and try again + if (pReply->resultCode == 404) { + if (hdrs[1].szName == nullptr) + return true; + + m_szWSCookie.Empty(); // don't use the same cookie twice + } + return false; + } + + // succeeded! + debugLogA("Gateway connection succeeded"); + m_hGatewayConnection = pReply->nlc; + + bool bExit = false; + int offset = 0; + MBinBuffer netbuf; + + while (!bExit) { + if (m_bTerminated) + break; + + unsigned char buf[2048]; + int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, MSG_NODUMP); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + + WSHeader hdr; + if (!WebSocket_InitHeader(hdr, buf, bufSize)) { + offset += bufSize; + continue; + } + offset = 0; + + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); + + // we have some additional data, not only opcode + if ((size_t)bufSize > hdr.headerSize) { + size_t currPacketSize = bufSize - hdr.headerSize; + netbuf.append(buf, bufSize); + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hGatewayConnection, (char*)buf, _countof(buf), MSG_NODUMP); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + currPacketSize += result; + netbuf.append(buf, result); + } + } + + // read all payloads from the current buffer, one by one + size_t prevSize = 0; + while (true) { + switch (hdr.opCode) { + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (hdr.bIsFinal) { + // process a packet here + CMStringA szJson((char*)netbuf.data() + hdr.headerSize, (int)hdr.payloadSize); + debugLogA("JSON received:\n%s", szJson.c_str()); + JSONNode root = JSONNode::parse(szJson); + if (root) + bExit = GatewayProcess(root); + } + break; + + case 8: // close + debugLogA("server required to exit"); + bExit = true; // simply reconnect, don't exit + break; + + case 9: // ping + debugLogA("ping received"); + Netlib_Send(m_hGatewayConnection, (char*)buf + hdr.headerSize, bufSize - int(hdr.headerSize), 0); + break; + } + + if (hdr.bIsFinal) + netbuf.remove(hdr.headerSize + hdr.payloadSize); + + if (netbuf.length() == 0) + break; + + // if we have not enough data for header, continue reading + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) + break; + + // if we have not enough data for data, continue reading + if (hdr.headerSize + hdr.payloadSize > netbuf.length()) + break; + + debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + if (prevSize == netbuf.length()) { + netbuf.remove(prevSize); + debugLogA("dropping current packet, exiting"); + break; + } + + prevSize = netbuf.length(); + } + } + + Netlib_CloseHandle(m_hGatewayConnection); + m_hGatewayConnection = nullptr; + return bExit; +} + +////////////////////////////////////////////////////////////////////////////////////// +// handles server commands + +bool CDiscordProto::GatewayProcess(const JSONNode &pRoot) +{ + int opCode = pRoot["op"].as_int(); + switch (opCode) { + case OPCODE_DISPATCH: // process incoming command + { + int iSeq = pRoot["s"].as_int(); + if (iSeq != 0) + m_iGatewaySeq = iSeq; + + CMStringW wszCommand = pRoot["t"].as_mstring(); + debugLogA("got a server command to dispatch: %S", wszCommand.c_str()); + + GatewayHandlerFunc pFunc = GetHandler(wszCommand); + if (pFunc) + (this->*pFunc)(pRoot["d"]); + } + break; + + case OPCODE_RECONNECT: // we need to reconnect asap + debugLogA("we need to reconnect, leaving worker thread"); + return true; + + case OPCODE_INVALID_SESSION: // session invalidated + if (pRoot["d"].as_bool()) // session can be resumed + GatewaySendResume(); + else { + Sleep(5000); // 5 seconds - recommended timeout + GatewaySendIdentify(); + } + break; + + case OPCODE_HELLO: // hello + m_iHartbeatInterval = pRoot["d"]["heartbeat_interval"].as_int(); + + GatewaySendIdentify(); + break; + + case OPCODE_HEARTBEAT_ACK: // heartbeat ack + break; + + default: + debugLogA("ACHTUNG! Unknown opcode: %d, report it to developer", opCode); + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////// +// requests to be sent to a gateway + +void CDiscordProto::GatewaySendGuildInfo(CDiscordGuild *pGuild) +{ + if (!pGuild->arChannels.getCount()) + return; + + JSONNode a1(JSON_ARRAY); a1 << INT_PARAM("", 0) << INT_PARAM("", 99); + + CMStringA szId(FORMAT, "%lld", pGuild->arChannels[0]->id); + JSONNode chl(JSON_ARRAY); chl.set_name(szId.c_str()); chl << a1; + + JSONNode channels; channels.set_name("channels"); channels << chl; + + JSONNode payload; payload.set_name("d"); + payload << SINT64_PARAM("guild_id", pGuild->id) << BOOL_PARAM("typing", true) << BOOL_PARAM("activities", true) << BOOL_PARAM("presences", true) << channels; + + JSONNode root; + root << INT_PARAM("op", OPCODE_REQUEST_SYNC_CHANNEL) << payload; + GatewaySend(root); +} + +void CDiscordProto::GatewaySendHeartbeat() +{ + // we don't send heartbeat packets until we get logged in + if (!m_iHartbeatInterval || !m_iGatewaySeq) + return; + + JSONNode root; + root << INT_PARAM("op", OPCODE_HEARTBEAT) << INT_PARAM("d", m_iGatewaySeq); + GatewaySend(root); +} + +void CDiscordProto::GatewaySendIdentify() +{ + if (m_szAccessToken == nullptr) { + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return; + } + + char szOs[256]; + OS_GetDisplayString(szOs, _countof(szOs)); + + char szVersion[256]; + Miranda_GetVersionText(szVersion, _countof(szVersion)); + + JSONNode props; props.set_name("properties"); + props << CHAR_PARAM("os", szOs) << CHAR_PARAM("browser", "Chrome") << CHAR_PARAM("device", szVersion) + << CHAR_PARAM("referrer", "https://miranda-ng.org") << CHAR_PARAM("referring_domain", "miranda-ng.org"); + + JSONNode payload; payload.set_name("d"); + payload << CHAR_PARAM("token", m_szAccessToken) << props << BOOL_PARAM("compress", false) << INT_PARAM("large_threshold", 250); + + JSONNode root; + root << INT_PARAM("op", OPCODE_IDENTIFY) << payload; + GatewaySend(root); +} + +void CDiscordProto::GatewaySendResume() +{ + char szRandom[40]; + uint8_t random[16]; + Utils_GetRandom(random, _countof(random)); + bin2hex(random, _countof(random), szRandom); + + JSONNode root; + root << CHAR_PARAM("token", szRandom) << CHAR_PARAM("session_id", m_szGatewaySessionId) << INT_PARAM("seq", m_iGatewaySeq); + GatewaySend(root); +} + +bool CDiscordProto::GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText) +{ + if (iStatus == ID_STATUS_OFFLINE) { + Push(new AsyncHttpRequest(this, REQUEST_POST, "/auth/logout", nullptr)); + return true; + } + + const char *pszStatus; + switch (iStatus) { + case ID_STATUS_AWAY: + case ID_STATUS_NA: + pszStatus = "idle"; break; + case ID_STATUS_DND: + pszStatus = "dnd"; break; + case ID_STATUS_INVISIBLE: + pszStatus = "invisible"; break; + default: + pszStatus = "online"; break; + } + + JSONNode payload; payload.set_name("d"); + payload << INT64_PARAM("since", __int64(time(0)) * 1000) << BOOL_PARAM("afk", true) << CHAR_PARAM("status", pszStatus); + if (pwszStatusText == nullptr) + payload << CHAR_PARAM("game", nullptr); + else { + JSONNode game; game.set_name("game"); game << WCHAR_PARAM("name", pwszStatusText) << INT_PARAM("type", 0); + payload << game; + } + + JSONNode root; root << INT_PARAM("op", OPCODE_STATUS_UPDATE) << payload; + return GatewaySend(root); +} diff --git a/protocols/Discord/src/groupchat.cpp b/protocols/Discord/src/groupchat.cpp index f34e35c93a..146f8de1fe 100644 --- a/protocols/Discord/src/groupchat.cpp +++ b/protocols/Discord/src/groupchat.cpp @@ -1,235 +1,235 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -enum { - IDM_CANCEL, - IDM_COPY_ID, - - IDM_CHANGENICK, IDM_CHANGETOPIC, IDM_RENAME, IDM_DESTROY -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -void BuildStatusList(const CDiscordGuild *pGuild, SESSION_INFO *si) -{ - Chat_AddGroup(si, L"@owner"); - - for (auto &it : pGuild->arRoles) - Chat_AddGroup(si, it->wszName); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static gc_item sttLogListItems[] = -{ - { LPGENW("Change &nickname"), IDM_CHANGENICK, MENU_ITEM }, - { LPGENW("Channel control"), FALSE, MENU_NEWPOPUP }, - { LPGENW("Change &topic"), IDM_CHANGETOPIC, MENU_POPUPITEM }, - { LPGENW("&Rename channel"), IDM_RENAME, MENU_POPUPITEM }, - { nullptr, 0, MENU_POPUPSEPARATOR }, - { LPGENW("&Destroy channel"), IDM_DESTROY, MENU_POPUPITEM }, -}; - -static gc_item sttNicklistItems[] = -{ - { LPGENW("Copy ID"), IDM_COPY_ID, MENU_ITEM }, -}; - -int CDiscordProto::GroupchatMenuHook(WPARAM, LPARAM lParam) -{ - GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; - if (gcmi == nullptr) - return 0; - - if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) - return 0; - - CDiscordUser *pChat = FindUserByChannel(_wtoi64(gcmi->pszID)); - if (pChat == nullptr) - return 0; - - if (gcmi->Type == MENU_ON_LOG) - Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin); - else if (gcmi->Type == MENU_ON_NICKLIST) - Chat_AddMenuItems(gcmi->hMenu, _countof(sttNicklistItems), sttNicklistItems, &g_plugin); - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::Chat_SendPrivateMessage(GCHOOK *gch) -{ - SnowFlake userId = _wtoi64(gch->ptszUID); - - MCONTACT hContact; - CDiscordUser *pUser = FindUser(userId); - if (pUser == nullptr) { - PROTOSEARCHRESULT psr = { sizeof(psr) }; - psr.id.w = (wchar_t*)gch->ptszUID; - psr.nick.w = (wchar_t*)gch->ptszNick; - if ((hContact = AddToList(PALF_TEMPORARY, &psr)) == 0) - return; - - setId(hContact, DB_KEY_ID, userId); - setId(hContact, DB_KEY_CHANNELID, _wtoi64(gch->si->ptszID)); - setWString(hContact, DB_KEY_NICK, gch->ptszNick); - Contact::Hide(hContact); - db_set_dw(hContact, "Ignore", "Mask1", 0); - } - else hContact = pUser->hContact; - - CallService(MS_MSG_SENDMESSAGE, hContact, 0); -} - -void CDiscordProto::Chat_ProcessLogMenu(GCHOOK *gch) -{ - CDiscordUser *pUser = FindUserByChannel(_wtoi64(gch->si->ptszID)); - if (pUser == nullptr) - return; - - ENTER_STRING es = {}; - es.szModuleName = m_szModuleName; - - switch (gch->dwData) { - case IDM_DESTROY: - if (IDYES == MessageBox(nullptr, TranslateT("Do you really want to destroy this channel? This action is non-revertable."), m_tszUserName, MB_YESNO | MB_ICONQUESTION)) { - CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); - Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, nullptr)); - } - break; - - case IDM_RENAME: - es.caption = TranslateT("Enter new channel name:"); - es.type = ESF_COMBO; - es.szDataPrefix = "chat_rename"; - if (EnterString(&es)) { - JSONNode root; root << WCHAR_PARAM("name", es.ptszResult); - CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); - Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); - mir_free(es.ptszResult); - } - break; - - case IDM_CHANGETOPIC: - es.caption = TranslateT("Enter new topic:"); - es.type = ESF_RICHEDIT; - es.szDataPrefix = "chat_topic"; - if (EnterString(&es)) { - JSONNode root; root << WCHAR_PARAM("topic", es.ptszResult); - CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); - Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); - mir_free(es.ptszResult); - } - break; - - case IDM_CHANGENICK: - es.caption = TranslateT("Enter your new nick name:"); - es.type = ESF_COMBO; - es.szDataPrefix = "chat_nick"; - es.recentCount = 5; - if (EnterString(&es)) { - JSONNode root; root << WCHAR_PARAM("nick", es.ptszResult); - CMStringA szUrl(FORMAT, "/guilds/%lld/members/@me/nick", pUser->pGuild->id); - Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); - mir_free(es.ptszResult); - } - break; - } -} - -void CDiscordProto::Chat_ProcessNickMenu(GCHOOK* gch) -{ - auto *pChannel = FindUserByChannel(_wtoi64(gch->si->ptszID)); - if (pChannel == nullptr || pChannel->pGuild == nullptr) - return; - - auto* pUser = pChannel->pGuild->FindUser(_wtoi64(gch->ptszUID)); - if (pUser == nullptr) - return; - - switch (gch->dwData) { - case IDM_COPY_ID: - CopyId(pUser->wszDiscordId); - break; - } -} - -int CDiscordProto::GroupchatEventHook(WPARAM, LPARAM lParam) -{ - GCHOOK *gch = (GCHOOK*)lParam; - if (gch == nullptr) - return 0; - - if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) - return 0; - - switch (gch->iType) { - case GC_USER_MESSAGE: - if (m_bOnline && mir_wstrlen(gch->ptszText) > 0) { - CMStringW wszText(gch->ptszText); - wszText.TrimRight(); - - int pos = wszText.Find(':'); - if (pos != -1) { - auto wszWord = wszText.Left(pos); - wszWord.Trim(); - if (auto *si = g_chatApi.SM_FindSession(gch->si->ptszID, gch->si->pszModule)) { - USERINFO *pUser = nullptr; - - for (auto &U : si->getUserList()) - if (wszWord == U->pszNick) { - pUser = U; - break; - } - - if (pUser) { - wszText.Delete(0, pos); - wszText.Insert(0, L"<@" + CMStringW(pUser->pszUID) + L">"); - } - } - } - - Chat_UnescapeTags(wszText.GetBuffer()); - - JSONNode body; body << WCHAR_PARAM("content", wszText); - CMStringA szUrl(FORMAT, "/channels/%S/messages", gch->si->ptszID); - Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr, &body)); - } - break; - - case GC_USER_PRIVMESS: - Chat_SendPrivateMessage(gch); - break; - - case GC_USER_LOGMENU: - Chat_ProcessLogMenu(gch); - break; - - case GC_USER_NICKLISTMENU: - Chat_ProcessNickMenu(gch); - break; - - case GC_USER_TYPNOTIFY: - UserIsTyping(gch->si->hContact, (int)gch->dwData); - break; - } - - return 1; -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +enum { + IDM_CANCEL, + IDM_COPY_ID, + + IDM_CHANGENICK, IDM_CHANGETOPIC, IDM_RENAME, IDM_DESTROY +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +void BuildStatusList(const CDiscordGuild *pGuild, SESSION_INFO *si) +{ + Chat_AddGroup(si, L"@owner"); + + for (auto &it : pGuild->arRoles) + Chat_AddGroup(si, it->wszName); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static gc_item sttLogListItems[] = +{ + { LPGENW("Change &nickname"), IDM_CHANGENICK, MENU_ITEM }, + { LPGENW("Channel control"), FALSE, MENU_NEWPOPUP }, + { LPGENW("Change &topic"), IDM_CHANGETOPIC, MENU_POPUPITEM }, + { LPGENW("&Rename channel"), IDM_RENAME, MENU_POPUPITEM }, + { nullptr, 0, MENU_POPUPSEPARATOR }, + { LPGENW("&Destroy channel"), IDM_DESTROY, MENU_POPUPITEM }, +}; + +static gc_item sttNicklistItems[] = +{ + { LPGENW("Copy ID"), IDM_COPY_ID, MENU_ITEM }, +}; + +int CDiscordProto::GroupchatMenuHook(WPARAM, LPARAM lParam) +{ + GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; + if (gcmi == nullptr) + return 0; + + if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) + return 0; + + CDiscordUser *pChat = FindUserByChannel(_wtoi64(gcmi->pszID)); + if (pChat == nullptr) + return 0; + + if (gcmi->Type == MENU_ON_LOG) + Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin); + else if (gcmi->Type == MENU_ON_NICKLIST) + Chat_AddMenuItems(gcmi->hMenu, _countof(sttNicklistItems), sttNicklistItems, &g_plugin); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::Chat_SendPrivateMessage(GCHOOK *gch) +{ + SnowFlake userId = _wtoi64(gch->ptszUID); + + MCONTACT hContact; + CDiscordUser *pUser = FindUser(userId); + if (pUser == nullptr) { + PROTOSEARCHRESULT psr = { sizeof(psr) }; + psr.id.w = (wchar_t*)gch->ptszUID; + psr.nick.w = (wchar_t*)gch->ptszNick; + if ((hContact = AddToList(PALF_TEMPORARY, &psr)) == 0) + return; + + setId(hContact, DB_KEY_ID, userId); + setId(hContact, DB_KEY_CHANNELID, _wtoi64(gch->si->ptszID)); + setWString(hContact, DB_KEY_NICK, gch->ptszNick); + Contact::Hide(hContact); + db_set_dw(hContact, "Ignore", "Mask1", 0); + } + else hContact = pUser->hContact; + + CallService(MS_MSG_SENDMESSAGE, hContact, 0); +} + +void CDiscordProto::Chat_ProcessLogMenu(GCHOOK *gch) +{ + CDiscordUser *pUser = FindUserByChannel(_wtoi64(gch->si->ptszID)); + if (pUser == nullptr) + return; + + ENTER_STRING es = {}; + es.szModuleName = m_szModuleName; + + switch (gch->dwData) { + case IDM_DESTROY: + if (IDYES == MessageBox(nullptr, TranslateT("Do you really want to destroy this channel? This action is non-revertable."), m_tszUserName, MB_YESNO | MB_ICONQUESTION)) { + CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); + Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, nullptr)); + } + break; + + case IDM_RENAME: + es.caption = TranslateT("Enter new channel name:"); + es.type = ESF_COMBO; + es.szDataPrefix = "chat_rename"; + if (EnterString(&es)) { + JSONNode root; root << WCHAR_PARAM("name", es.ptszResult); + CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); + mir_free(es.ptszResult); + } + break; + + case IDM_CHANGETOPIC: + es.caption = TranslateT("Enter new topic:"); + es.type = ESF_RICHEDIT; + es.szDataPrefix = "chat_topic"; + if (EnterString(&es)) { + JSONNode root; root << WCHAR_PARAM("topic", es.ptszResult); + CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); + mir_free(es.ptszResult); + } + break; + + case IDM_CHANGENICK: + es.caption = TranslateT("Enter your new nick name:"); + es.type = ESF_COMBO; + es.szDataPrefix = "chat_nick"; + es.recentCount = 5; + if (EnterString(&es)) { + JSONNode root; root << WCHAR_PARAM("nick", es.ptszResult); + CMStringA szUrl(FORMAT, "/guilds/%lld/members/@me/nick", pUser->pGuild->id); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); + mir_free(es.ptszResult); + } + break; + } +} + +void CDiscordProto::Chat_ProcessNickMenu(GCHOOK* gch) +{ + auto *pChannel = FindUserByChannel(_wtoi64(gch->si->ptszID)); + if (pChannel == nullptr || pChannel->pGuild == nullptr) + return; + + auto* pUser = pChannel->pGuild->FindUser(_wtoi64(gch->ptszUID)); + if (pUser == nullptr) + return; + + switch (gch->dwData) { + case IDM_COPY_ID: + CopyId(pUser->wszDiscordId); + break; + } +} + +int CDiscordProto::GroupchatEventHook(WPARAM, LPARAM lParam) +{ + GCHOOK *gch = (GCHOOK*)lParam; + if (gch == nullptr) + return 0; + + if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) + return 0; + + switch (gch->iType) { + case GC_USER_MESSAGE: + if (m_bOnline && mir_wstrlen(gch->ptszText) > 0) { + CMStringW wszText(gch->ptszText); + wszText.TrimRight(); + + int pos = wszText.Find(':'); + if (pos != -1) { + auto wszWord = wszText.Left(pos); + wszWord.Trim(); + if (auto *si = g_chatApi.SM_FindSession(gch->si->ptszID, gch->si->pszModule)) { + USERINFO *pUser = nullptr; + + for (auto &U : si->getUserList()) + if (wszWord == U->pszNick) { + pUser = U; + break; + } + + if (pUser) { + wszText.Delete(0, pos); + wszText.Insert(0, L"<@" + CMStringW(pUser->pszUID) + L">"); + } + } + } + + Chat_UnescapeTags(wszText.GetBuffer()); + + JSONNode body; body << WCHAR_PARAM("content", wszText); + CMStringA szUrl(FORMAT, "/channels/%S/messages", gch->si->ptszID); + Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr, &body)); + } + break; + + case GC_USER_PRIVMESS: + Chat_SendPrivateMessage(gch); + break; + + case GC_USER_LOGMENU: + Chat_ProcessLogMenu(gch); + break; + + case GC_USER_NICKLISTMENU: + Chat_ProcessNickMenu(gch); + break; + + case GC_USER_TYPNOTIFY: + UserIsTyping(gch->si->hContact, (int)gch->dwData); + break; + } + + return 1; +} diff --git a/protocols/Discord/src/guilds.cpp b/protocols/Discord/src/guilds.cpp index d05ff80863..760437ceb0 100644 --- a/protocols/Discord/src/guilds.cpp +++ b/protocols/Discord/src/guilds.cpp @@ -1,413 +1,413 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2); - -static int compareRoles(const CDiscordRole *p1, const CDiscordRole *p2) -{ - return compareInt64(p1->id, p2->id); -} - -static int compareChatUsers(const CDiscordGuildMember *p1, const CDiscordGuildMember *p2) -{ - return compareInt64(p1->userId, p2->userId); -} - -CDiscordGuild::CDiscordGuild(SnowFlake _id) : - id(_id), - arChannels(10, compareUsers), - arChatUsers(30, compareChatUsers), - arRoles(10, compareRoles) -{ -} - -CDiscordGuild::~CDiscordGuild() -{ -} - -CDiscordUser::~CDiscordUser() -{ - if (pGuild != nullptr) - pGuild->arChannels.remove(this); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reads a presence block from json - -void CDiscordProto::ProcessPresence(const JSONNode &root) -{ - auto userId = ::getId(root["user"]["id"]); - CDiscordUser *pUser = FindUser(userId); - if (pUser == nullptr) { - debugLogA("Presence from unknown user id %lld ignored", userId); - return; - } - - setWord(pUser->hContact, "Status", StrToStatus(root["status"].as_mstring())); - - CheckAvatarChange(pUser->hContact, root["user"]["avatar"].as_mstring()); - - for (auto &act : root["activities"]) { - CMStringW wszStatus(act["state"].as_mstring()); - if (!wszStatus.IsEmpty()) - db_set_ws(pUser->hContact, "CList", "StatusMsg", wszStatus); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reads a role from json - -void CDiscordProto::ProcessRole(CDiscordGuild *guild, const JSONNode &role) -{ - SnowFlake id = ::getId(role["id"]); - CDiscordRole *p = guild->arRoles.find((CDiscordRole*)&id); - if (p == nullptr) { - p = new CDiscordRole(); - p->id = id; - guild->arRoles.insert(p); - } - - p->color = role["color"].as_int(); - p->position = role["position"].as_int(); - p->permissions = role["permissions"].as_int(); - p->wszName = role["name"].as_mstring(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static void sttSetGroupName(MCONTACT hContact, const wchar_t *pwszGroupName) -{ - ptrW wszOldName(Clist_GetGroup(hContact)); - if (wszOldName != nullptr) { - ptrW wszChatGroup(Chat_GetGroup()); - if (mir_wstrcmpi(wszOldName, wszChatGroup)) - return; // custom group, don't touch it - } - - Clist_SetGroup(hContact, pwszGroupName); -} - -void CDiscordProto::BatchChatCreate(void *param) -{ - CDiscordGuild *pGuild = (CDiscordGuild*)param; - - for (auto &it : pGuild->arChannels) - if (!it->bIsPrivate && !it->bIsGroup) - CreateChat(pGuild, it); -} - -void CDiscordProto::CreateChat(CDiscordGuild *pGuild, CDiscordUser *pUser) -{ - SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, pUser->wszUsername, pUser->wszChannelName); - si->pParent = pGuild->pParentSi; - pUser->hContact = si->hContact; - setId(pUser->hContact, DB_KEY_ID, pUser->channelId); - setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId); - - SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID); - if (oldMsgId == 0) - RetrieveHistory(pUser, MSG_BEFORE, pUser->lastMsgId, 20); - else if (!pUser->bSynced && pUser->lastMsgId > oldMsgId) { - pUser->bSynced = true; - RetrieveHistory(pUser, MSG_AFTER, oldMsgId, 99); - } - - if (m_bUseGuildGroups) { - if (pUser->parentId) { - CDiscordUser *pParent = FindUserByChannel(pUser->parentId); - if (pParent != nullptr) - sttSetGroupName(pUser->hContact, pParent->wszChannelName); - } - else sttSetGroupName(pUser->hContact, Clist_GroupGetName(pGuild->groupId)); - } - - BuildStatusList(pGuild, si); - - Chat_Control(m_szModuleName, pUser->wszUsername, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); - Chat_Control(m_szModuleName, pUser->wszUsername, SESSION_ONLINE); - - if (!pUser->wszTopic.IsEmpty()) { - Chat_SetStatusbarText(m_szModuleName, pUser->wszUsername, pUser->wszTopic); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; - gce.pszID.w = pUser->wszUsername; - gce.time = time(0); - gce.pszText.w = pUser->wszTopic; - Chat_Event(&gce); - } -} - -void CDiscordProto::ProcessGuild(const JSONNode &pRoot) -{ - SnowFlake guildId = ::getId(pRoot["id"]); - - CDiscordGuild *pGuild = FindGuild(guildId); - if (pGuild == nullptr) { - pGuild = new CDiscordGuild(guildId); - pGuild->LoadFromFile(); - arGuilds.insert(pGuild); - } - - pGuild->ownerId = ::getId(pRoot["owner_id"]); - pGuild->wszName = pRoot["name"].as_mstring(); - if (m_bUseGuildGroups) - pGuild->groupId = Clist_GroupCreate(Clist_GroupExists(m_wszDefaultGroup), pGuild->wszName); - - SESSION_INFO *si = Chat_NewSession(GCW_SERVER, m_szModuleName, pGuild->wszName, pGuild->wszName, pGuild); - if (si == nullptr) - return; - - pGuild->pParentSi = (SESSION_INFO*)si; - pGuild->hContact = si->hContact; - setId(pGuild->hContact, DB_KEY_CHANNELID, guildId); - - Chat_Control(m_szModuleName, pGuild->wszName, WINDOW_HIDDEN); - Chat_Control(m_szModuleName, pGuild->wszName, SESSION_ONLINE); - - for (auto &it : pRoot["roles"]) - ProcessRole(pGuild, it); - - BuildStatusList(pGuild, si); - - for (auto &it : pRoot["channels"]) - ProcessGuildChannel(pGuild, it); - - if (!pGuild->bSynced && getByte(si->hContact, "EnableSync")) - GatewaySendGuildInfo(pGuild); - - // store all guild members - for (auto &it : pRoot["members"]) { - auto *pm = ProcessGuildUser(pGuild, it); - - CMStringW wszNick = it["nick"].as_mstring(); - if (!wszNick.IsEmpty()) - pm->wszNick = wszNick; - - pm->iStatus = ID_STATUS_OFFLINE; - } - - // parse online statuses - for (auto &it : pRoot["presences"]) { - CDiscordGuildMember *gm = pGuild->FindUser(::getId(it["user"]["id"])); - if (gm != nullptr) - gm->iStatus = StrToStatus(it["status"].as_mstring()); - } - - for (auto &it : pGuild->arChatUsers) - AddGuildUser(pGuild, *it); - - if (!m_bTerminated) - ForkThread(&CDiscordProto::BatchChatCreate, pGuild); - - pGuild->bSynced = true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CDiscordUser* CDiscordProto::ProcessGuildChannel(CDiscordGuild *pGuild, const JSONNode &pch) -{ - CMStringW wszChannelId = pch["id"].as_mstring(); - SnowFlake channelId = _wtoi64(wszChannelId); - CMStringW wszName = pch["name"].as_mstring(); - CDiscordUser *pUser; - - // filter our all channels but the text ones - switch (pch["type"].as_int()) { - case 4: // channel group - if (!m_bUseGuildGroups) // ignore groups when they aren't enabled - return nullptr; - - pUser = FindUserByChannel(channelId); - if (pUser == nullptr) { - // missing channel - create it - pUser = new CDiscordUser(channelId); - pUser->bIsPrivate = false; - pUser->channelId = channelId; - pUser->bIsGroup = true; - arUsers.insert(pUser); - - pGuild->arChannels.insert(pUser); - - MGROUP grpId = Clist_GroupCreate(pGuild->groupId, wszName); - pUser->wszChannelName = Clist_GroupGetName(grpId); - } - return pUser; - - case 0: // text channel - pUser = FindUserByChannel(channelId); - if (pUser == nullptr) { - // missing channel - create it - pUser = new CDiscordUser(channelId); - pUser->bIsPrivate = false; - pUser->channelId = channelId; - arUsers.insert(pUser); - } - - if (pGuild->arChannels.find(pUser) == nullptr) - pGuild->arChannels.insert(pUser); - - pUser->wszUsername = wszChannelId; - if (m_bUseGuildGroups) - pUser->wszChannelName = L"#" + wszName; - else - pUser->wszChannelName = pGuild->wszName + L"#" + wszName; - pUser->wszTopic = pch["topic"].as_mstring(); - pUser->pGuild = pGuild; - pUser->lastMsgId = ::getId(pch["last_message_id"]); - pUser->parentId = _wtoi64(pch["parent_id"].as_mstring()); - return pUser; - } - - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CDiscordGuildMember* CDiscordProto::ProcessGuildUser(CDiscordGuild *pGuild, const JSONNode &pRoot, bool *pbNew) -{ - auto& pUser = pRoot["user"]; - - bool bNew = false; - CMStringW wszUserId = pUser["id"].as_mstring(); - SnowFlake userId = _wtoi64(wszUserId); - CDiscordGuildMember *pm = pGuild->FindUser(userId); - if (pm == nullptr) { - pm = new CDiscordGuildMember(userId); - pGuild->arChatUsers.insert(pm); - bNew = true; - } - - pm->wszDiscordId = pUser["username"].as_mstring() + L"#" + pUser["discriminator"].as_mstring(); - pm->wszNick = pRoot["nick"].as_mstring(); - if (pm->wszNick.IsEmpty()) - pm->wszNick = pUser["username"].as_mstring(); - else - bNew = true; - - if (userId == pGuild->ownerId) - pm->wszRole = L"@owner"; - else { - CDiscordRole *pRole = nullptr; - for (auto &itr : pRoot["roles"]) { - SnowFlake roleId = ::getId(itr); - if (pRole = pGuild->arRoles.find((CDiscordRole *)&roleId)) - break; - } - pm->wszRole = (pRole == nullptr) ? L"@everyone" : pRole->wszName; - } - - if (pbNew) - *pbNew = bNew; - return pm; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::ProcessChatUser(CDiscordUser *pChat, const CMStringW &wszUserId, const JSONNode &pRoot) -{ - // input data control - SnowFlake userId = _wtoi64(wszUserId); - CDiscordGuild *pGuild = pChat->pGuild; - if (pGuild == nullptr || userId == 0) - return; - - // does user exist? if yes, there's nothing to do - auto *pm = pGuild->FindUser(userId); - if (pm != nullptr) - return; - - // otherwise let's create a user and insert him into all guild's chats - pm = new CDiscordGuildMember(userId); - pm->wszDiscordId = pRoot["author"]["username"].as_mstring() + L"#" + pRoot["author"]["discriminator"].as_mstring(); - pm->wszNick = pRoot["nick"].as_mstring(); - if (pm->wszNick.IsEmpty()) - pm->wszNick = pRoot["author"]["username"].as_mstring(); - pGuild->arChatUsers.insert(pm); - - debugLogA("add missing user to chat: id=%lld, nick=%S", userId, pm->wszNick.c_str()); - AddGuildUser(pGuild, *pm); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::AddGuildUser(CDiscordGuild *pGuild, const CDiscordGuildMember &pUser) -{ - int flags = 0; - switch (pUser.iStatus) { - case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_DND: - flags = 1; - break; - } - - auto *pStatus = g_chatApi.TM_FindStatus(pGuild->pParentSi->pStatuses, pUser.wszRole); - - wchar_t wszUserId[100]; - _i64tow_s(pUser.userId, wszUserId, _countof(wszUserId), 10); - - auto *pu = g_chatApi.UM_AddUser(pGuild->pParentSi, wszUserId, pUser.wszNick, (pStatus) ? pStatus->iStatus : 0); - pu->iStatusEx = flags; - if (pUser.userId == m_ownId) - pGuild->pParentSi->pMe = pu; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordGuild::LoadFromFile() -{ - int fileNo = _wopen(GetCacheFile(), O_TEXT | O_RDONLY); - if (fileNo == -1) - return; - - int fSize = ::filelength(fileNo); - ptrA json((char*)mir_alloc(fSize + 1)); - read(fileNo, json, fSize); - close(fileNo); - - JSONNode cached = JSONNode::parse(json); - for (auto &it : cached) { - SnowFlake userId = getId(it["id"]); - auto *pUser = FindUser(userId); - if (pUser == nullptr) { - pUser = new CDiscordGuildMember(userId); - arChatUsers.insert(pUser); - } - - pUser->wszNick = it["n"].as_mstring(); - pUser->wszRole = it["r"].as_mstring(); - } -} - -void CDiscordGuild ::SaveToFile() -{ - JSONNode members(JSON_ARRAY); - for (auto &it : arChatUsers) { - JSONNode member; - member << INT64_PARAM("id", it->userId) << WCHAR_PARAM("n", it->wszNick) << WCHAR_PARAM("r", it->wszRole); - members << member; - } - - CMStringW wszFileName(GetCacheFile()); - CreatePathToFileW(wszFileName); - int fileNo = _wopen(wszFileName, O_CREAT | O_TRUNC | O_TEXT | O_WRONLY); - if (fileNo != -1) { - std::string json = members.write_formatted(); - write(fileNo, json.c_str(), (int)json.size()); - close(fileNo); - } -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2); + +static int compareRoles(const CDiscordRole *p1, const CDiscordRole *p2) +{ + return compareInt64(p1->id, p2->id); +} + +static int compareChatUsers(const CDiscordGuildMember *p1, const CDiscordGuildMember *p2) +{ + return compareInt64(p1->userId, p2->userId); +} + +CDiscordGuild::CDiscordGuild(SnowFlake _id) : + id(_id), + arChannels(10, compareUsers), + arChatUsers(30, compareChatUsers), + arRoles(10, compareRoles) +{ +} + +CDiscordGuild::~CDiscordGuild() +{ +} + +CDiscordUser::~CDiscordUser() +{ + if (pGuild != nullptr) + pGuild->arChannels.remove(this); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reads a presence block from json + +void CDiscordProto::ProcessPresence(const JSONNode &root) +{ + auto userId = ::getId(root["user"]["id"]); + CDiscordUser *pUser = FindUser(userId); + if (pUser == nullptr) { + debugLogA("Presence from unknown user id %lld ignored", userId); + return; + } + + setWord(pUser->hContact, "Status", StrToStatus(root["status"].as_mstring())); + + CheckAvatarChange(pUser->hContact, root["user"]["avatar"].as_mstring()); + + for (auto &act : root["activities"]) { + CMStringW wszStatus(act["state"].as_mstring()); + if (!wszStatus.IsEmpty()) + db_set_ws(pUser->hContact, "CList", "StatusMsg", wszStatus); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reads a role from json + +void CDiscordProto::ProcessRole(CDiscordGuild *guild, const JSONNode &role) +{ + SnowFlake id = ::getId(role["id"]); + CDiscordRole *p = guild->arRoles.find((CDiscordRole*)&id); + if (p == nullptr) { + p = new CDiscordRole(); + p->id = id; + guild->arRoles.insert(p); + } + + p->color = role["color"].as_int(); + p->position = role["position"].as_int(); + p->permissions = role["permissions"].as_int(); + p->wszName = role["name"].as_mstring(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void sttSetGroupName(MCONTACT hContact, const wchar_t *pwszGroupName) +{ + ptrW wszOldName(Clist_GetGroup(hContact)); + if (wszOldName != nullptr) { + ptrW wszChatGroup(Chat_GetGroup()); + if (mir_wstrcmpi(wszOldName, wszChatGroup)) + return; // custom group, don't touch it + } + + Clist_SetGroup(hContact, pwszGroupName); +} + +void CDiscordProto::BatchChatCreate(void *param) +{ + CDiscordGuild *pGuild = (CDiscordGuild*)param; + + for (auto &it : pGuild->arChannels) + if (!it->bIsPrivate && !it->bIsGroup) + CreateChat(pGuild, it); +} + +void CDiscordProto::CreateChat(CDiscordGuild *pGuild, CDiscordUser *pUser) +{ + SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, pUser->wszUsername, pUser->wszChannelName); + si->pParent = pGuild->pParentSi; + pUser->hContact = si->hContact; + setId(pUser->hContact, DB_KEY_ID, pUser->channelId); + setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId); + + SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID); + if (oldMsgId == 0) + RetrieveHistory(pUser, MSG_BEFORE, pUser->lastMsgId, 20); + else if (!pUser->bSynced && pUser->lastMsgId > oldMsgId) { + pUser->bSynced = true; + RetrieveHistory(pUser, MSG_AFTER, oldMsgId, 99); + } + + if (m_bUseGuildGroups) { + if (pUser->parentId) { + CDiscordUser *pParent = FindUserByChannel(pUser->parentId); + if (pParent != nullptr) + sttSetGroupName(pUser->hContact, pParent->wszChannelName); + } + else sttSetGroupName(pUser->hContact, Clist_GroupGetName(pGuild->groupId)); + } + + BuildStatusList(pGuild, si); + + Chat_Control(m_szModuleName, pUser->wszUsername, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); + Chat_Control(m_szModuleName, pUser->wszUsername, SESSION_ONLINE); + + if (!pUser->wszTopic.IsEmpty()) { + Chat_SetStatusbarText(m_szModuleName, pUser->wszUsername, pUser->wszTopic); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; + gce.pszID.w = pUser->wszUsername; + gce.time = time(0); + gce.pszText.w = pUser->wszTopic; + Chat_Event(&gce); + } +} + +void CDiscordProto::ProcessGuild(const JSONNode &pRoot) +{ + SnowFlake guildId = ::getId(pRoot["id"]); + + CDiscordGuild *pGuild = FindGuild(guildId); + if (pGuild == nullptr) { + pGuild = new CDiscordGuild(guildId); + pGuild->LoadFromFile(); + arGuilds.insert(pGuild); + } + + pGuild->ownerId = ::getId(pRoot["owner_id"]); + pGuild->wszName = pRoot["name"].as_mstring(); + if (m_bUseGuildGroups) + pGuild->groupId = Clist_GroupCreate(Clist_GroupExists(m_wszDefaultGroup), pGuild->wszName); + + SESSION_INFO *si = Chat_NewSession(GCW_SERVER, m_szModuleName, pGuild->wszName, pGuild->wszName, pGuild); + if (si == nullptr) + return; + + pGuild->pParentSi = (SESSION_INFO*)si; + pGuild->hContact = si->hContact; + setId(pGuild->hContact, DB_KEY_CHANNELID, guildId); + + Chat_Control(m_szModuleName, pGuild->wszName, WINDOW_HIDDEN); + Chat_Control(m_szModuleName, pGuild->wszName, SESSION_ONLINE); + + for (auto &it : pRoot["roles"]) + ProcessRole(pGuild, it); + + BuildStatusList(pGuild, si); + + for (auto &it : pRoot["channels"]) + ProcessGuildChannel(pGuild, it); + + if (!pGuild->bSynced && getByte(si->hContact, "EnableSync")) + GatewaySendGuildInfo(pGuild); + + // store all guild members + for (auto &it : pRoot["members"]) { + auto *pm = ProcessGuildUser(pGuild, it); + + CMStringW wszNick = it["nick"].as_mstring(); + if (!wszNick.IsEmpty()) + pm->wszNick = wszNick; + + pm->iStatus = ID_STATUS_OFFLINE; + } + + // parse online statuses + for (auto &it : pRoot["presences"]) { + CDiscordGuildMember *gm = pGuild->FindUser(::getId(it["user"]["id"])); + if (gm != nullptr) + gm->iStatus = StrToStatus(it["status"].as_mstring()); + } + + for (auto &it : pGuild->arChatUsers) + AddGuildUser(pGuild, *it); + + if (!m_bTerminated) + ForkThread(&CDiscordProto::BatchChatCreate, pGuild); + + pGuild->bSynced = true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CDiscordUser* CDiscordProto::ProcessGuildChannel(CDiscordGuild *pGuild, const JSONNode &pch) +{ + CMStringW wszChannelId = pch["id"].as_mstring(); + SnowFlake channelId = _wtoi64(wszChannelId); + CMStringW wszName = pch["name"].as_mstring(); + CDiscordUser *pUser; + + // filter our all channels but the text ones + switch (pch["type"].as_int()) { + case 4: // channel group + if (!m_bUseGuildGroups) // ignore groups when they aren't enabled + return nullptr; + + pUser = FindUserByChannel(channelId); + if (pUser == nullptr) { + // missing channel - create it + pUser = new CDiscordUser(channelId); + pUser->bIsPrivate = false; + pUser->channelId = channelId; + pUser->bIsGroup = true; + arUsers.insert(pUser); + + pGuild->arChannels.insert(pUser); + + MGROUP grpId = Clist_GroupCreate(pGuild->groupId, wszName); + pUser->wszChannelName = Clist_GroupGetName(grpId); + } + return pUser; + + case 0: // text channel + pUser = FindUserByChannel(channelId); + if (pUser == nullptr) { + // missing channel - create it + pUser = new CDiscordUser(channelId); + pUser->bIsPrivate = false; + pUser->channelId = channelId; + arUsers.insert(pUser); + } + + if (pGuild->arChannels.find(pUser) == nullptr) + pGuild->arChannels.insert(pUser); + + pUser->wszUsername = wszChannelId; + if (m_bUseGuildGroups) + pUser->wszChannelName = L"#" + wszName; + else + pUser->wszChannelName = pGuild->wszName + L"#" + wszName; + pUser->wszTopic = pch["topic"].as_mstring(); + pUser->pGuild = pGuild; + pUser->lastMsgId = ::getId(pch["last_message_id"]); + pUser->parentId = _wtoi64(pch["parent_id"].as_mstring()); + return pUser; + } + + return nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CDiscordGuildMember* CDiscordProto::ProcessGuildUser(CDiscordGuild *pGuild, const JSONNode &pRoot, bool *pbNew) +{ + auto& pUser = pRoot["user"]; + + bool bNew = false; + CMStringW wszUserId = pUser["id"].as_mstring(); + SnowFlake userId = _wtoi64(wszUserId); + CDiscordGuildMember *pm = pGuild->FindUser(userId); + if (pm == nullptr) { + pm = new CDiscordGuildMember(userId); + pGuild->arChatUsers.insert(pm); + bNew = true; + } + + pm->wszDiscordId = pUser["username"].as_mstring() + L"#" + pUser["discriminator"].as_mstring(); + pm->wszNick = pRoot["nick"].as_mstring(); + if (pm->wszNick.IsEmpty()) + pm->wszNick = pUser["username"].as_mstring(); + else + bNew = true; + + if (userId == pGuild->ownerId) + pm->wszRole = L"@owner"; + else { + CDiscordRole *pRole = nullptr; + for (auto &itr : pRoot["roles"]) { + SnowFlake roleId = ::getId(itr); + if (pRole = pGuild->arRoles.find((CDiscordRole *)&roleId)) + break; + } + pm->wszRole = (pRole == nullptr) ? L"@everyone" : pRole->wszName; + } + + if (pbNew) + *pbNew = bNew; + return pm; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::ProcessChatUser(CDiscordUser *pChat, const CMStringW &wszUserId, const JSONNode &pRoot) +{ + // input data control + SnowFlake userId = _wtoi64(wszUserId); + CDiscordGuild *pGuild = pChat->pGuild; + if (pGuild == nullptr || userId == 0) + return; + + // does user exist? if yes, there's nothing to do + auto *pm = pGuild->FindUser(userId); + if (pm != nullptr) + return; + + // otherwise let's create a user and insert him into all guild's chats + pm = new CDiscordGuildMember(userId); + pm->wszDiscordId = pRoot["author"]["username"].as_mstring() + L"#" + pRoot["author"]["discriminator"].as_mstring(); + pm->wszNick = pRoot["nick"].as_mstring(); + if (pm->wszNick.IsEmpty()) + pm->wszNick = pRoot["author"]["username"].as_mstring(); + pGuild->arChatUsers.insert(pm); + + debugLogA("add missing user to chat: id=%lld, nick=%S", userId, pm->wszNick.c_str()); + AddGuildUser(pGuild, *pm); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::AddGuildUser(CDiscordGuild *pGuild, const CDiscordGuildMember &pUser) +{ + int flags = 0; + switch (pUser.iStatus) { + case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_DND: + flags = 1; + break; + } + + auto *pStatus = g_chatApi.TM_FindStatus(pGuild->pParentSi->pStatuses, pUser.wszRole); + + wchar_t wszUserId[100]; + _i64tow_s(pUser.userId, wszUserId, _countof(wszUserId), 10); + + auto *pu = g_chatApi.UM_AddUser(pGuild->pParentSi, wszUserId, pUser.wszNick, (pStatus) ? pStatus->iStatus : 0); + pu->iStatusEx = flags; + if (pUser.userId == m_ownId) + pGuild->pParentSi->pMe = pu; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordGuild::LoadFromFile() +{ + int fileNo = _wopen(GetCacheFile(), O_TEXT | O_RDONLY); + if (fileNo == -1) + return; + + int fSize = ::filelength(fileNo); + ptrA json((char*)mir_alloc(fSize + 1)); + read(fileNo, json, fSize); + close(fileNo); + + JSONNode cached = JSONNode::parse(json); + for (auto &it : cached) { + SnowFlake userId = getId(it["id"]); + auto *pUser = FindUser(userId); + if (pUser == nullptr) { + pUser = new CDiscordGuildMember(userId); + arChatUsers.insert(pUser); + } + + pUser->wszNick = it["n"].as_mstring(); + pUser->wszRole = it["r"].as_mstring(); + } +} + +void CDiscordGuild ::SaveToFile() +{ + JSONNode members(JSON_ARRAY); + for (auto &it : arChatUsers) { + JSONNode member; + member << INT64_PARAM("id", it->userId) << WCHAR_PARAM("n", it->wszNick) << WCHAR_PARAM("r", it->wszRole); + members << member; + } + + CMStringW wszFileName(GetCacheFile()); + CreatePathToFileW(wszFileName); + int fileNo = _wopen(wszFileName, O_CREAT | O_TRUNC | O_TEXT | O_WRONLY); + if (fileNo != -1) { + std::string json = members.write_formatted(); + write(fileNo, json.c_str(), (int)json.size()); + close(fileNo); + } +} diff --git a/protocols/Discord/src/http.cpp b/protocols/Discord/src/http.cpp index 2facf00af7..f65451f9ce 100644 --- a/protocols/Discord/src/http.cpp +++ b/protocols/Discord/src/http.cpp @@ -1,155 +1,155 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -void CDiscordProto::Push(AsyncHttpRequest *pReq, int iTimeout) -{ - pReq->timeout = iTimeout; - { - mir_cslock lck(m_csHttpQueue); - m_arHttpQueue.insert(pReq); - } - SetEvent(m_evRequestsQueue); -} - -void CDiscordProto::SaveToken(const JSONNode &data) -{ - CMStringA szToken = data["token"].as_mstring(); - if (!szToken.IsEmpty()) - m_szTempToken = szToken.Detach(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static LONG g_reqNum = 0; - -AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR _url, MTHttpRequestHandler pFunc, JSONNode *pRoot) -{ - if (*_url == '/') { // relative url leads to a site - m_szUrl = "https://discord.com/api/v8"; - m_szUrl += _url; - m_bMainSite = true; - } - else { - m_szUrl = _url; - m_bMainSite = false; - } - - flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_SSL; - if (ppro->m_szAccessToken != nullptr) { - AddHeader("Authorization", ppro->m_szAccessToken); - flags |= NLHRF_DUMPASTEXT | NLHRF_NODUMPHEADERS; - } - else flags |= NLHRF_NODUMPSEND; - - if (pRoot != nullptr) { - ptrW text(json_write(pRoot)); - pData = mir_utf8encodeW(text); - dataLength = (int)mir_strlen(pData); - - AddHeader("Content-Type", "application/json"); - } - - m_pFunc = pFunc; - requestType = iRequestType; - m_iErrorCode = 0; - m_iReqNum = ::InterlockedIncrement(&g_reqNum); -} - -JsonReply::JsonReply(NETLIBHTTPREQUEST *pReply) -{ - if (pReply == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = pReply->resultCode; - - m_root = json_parse(pReply->pData); - if (m_root == nullptr) - m_errorCode = 500; -} - -JsonReply::~JsonReply() -{ - json_delete(m_root); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::ServerThread(void*) -{ - m_szAccessToken = getStringA("AccessToken"); - m_hAPIConnection = nullptr; - m_bTerminated = false; - - debugLogA("CDiscordProto::WorkerThread: %s", "entering"); - - if (m_szAccessToken != nullptr) - RetrieveMyInfo(); // try to receive a response from server - else { - if (mir_wstrlen(m_wszEmail) == 0) { - ConnectionFailed(LOGINERR_BADUSERID); - return; - } - - ptrW wszPassword(getWStringA(DB_KEY_PASSWORD)); - if (wszPassword == nullptr) { - ConnectionFailed(LOGINERR_WRONGPASSWORD); - return; - } - - JSONNode root; root << WCHAR_PARAM("email", m_wszEmail) << WCHAR_PARAM("password", wszPassword); - Push(new AsyncHttpRequest(this, REQUEST_POST, "/auth/login", &CDiscordProto::OnReceiveToken, &root)); - } - - while (true) { - WaitForSingleObject(m_evRequestsQueue, 1000); - if (m_bTerminated) - break; - - AsyncHttpRequest *pReq; - bool need_sleep = false; - while (true) { - { - mir_cslock lck(m_csHttpQueue); - if (m_arHttpQueue.getCount() == 0) - break; - - pReq = m_arHttpQueue[0]; - m_arHttpQueue.remove(0); - need_sleep = (m_arHttpQueue.getCount() > 1); - } - if (m_bTerminated) - break; - ExecuteRequest(pReq); - if (need_sleep) { - Sleep(330); - debugLogA("CDiscordProto::WorkerThread: %s", "need to sleep"); - } - } - } - - m_hWorkerThread = nullptr; - if (m_hAPIConnection) { - Netlib_CloseHandle(m_hAPIConnection); - m_hAPIConnection = nullptr; - } - - debugLogA("CDiscordProto::WorkerThread: %s", "leaving"); -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +void CDiscordProto::Push(AsyncHttpRequest *pReq, int iTimeout) +{ + pReq->timeout = iTimeout; + { + mir_cslock lck(m_csHttpQueue); + m_arHttpQueue.insert(pReq); + } + SetEvent(m_evRequestsQueue); +} + +void CDiscordProto::SaveToken(const JSONNode &data) +{ + CMStringA szToken = data["token"].as_mstring(); + if (!szToken.IsEmpty()) + m_szTempToken = szToken.Detach(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static LONG g_reqNum = 0; + +AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR _url, MTHttpRequestHandler pFunc, JSONNode *pRoot) +{ + if (*_url == '/') { // relative url leads to a site + m_szUrl = "https://discord.com/api/v8"; + m_szUrl += _url; + m_bMainSite = true; + } + else { + m_szUrl = _url; + m_bMainSite = false; + } + + flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_SSL; + if (ppro->m_szAccessToken != nullptr) { + AddHeader("Authorization", ppro->m_szAccessToken); + flags |= NLHRF_DUMPASTEXT | NLHRF_NODUMPHEADERS; + } + else flags |= NLHRF_NODUMPSEND; + + if (pRoot != nullptr) { + ptrW text(json_write(pRoot)); + pData = mir_utf8encodeW(text); + dataLength = (int)mir_strlen(pData); + + AddHeader("Content-Type", "application/json"); + } + + m_pFunc = pFunc; + requestType = iRequestType; + m_iErrorCode = 0; + m_iReqNum = ::InterlockedIncrement(&g_reqNum); +} + +JsonReply::JsonReply(NETLIBHTTPREQUEST *pReply) +{ + if (pReply == nullptr) { + m_errorCode = 500; + return; + } + + m_errorCode = pReply->resultCode; + + m_root = json_parse(pReply->pData); + if (m_root == nullptr) + m_errorCode = 500; +} + +JsonReply::~JsonReply() +{ + json_delete(m_root); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::ServerThread(void*) +{ + m_szAccessToken = getStringA("AccessToken"); + m_hAPIConnection = nullptr; + m_bTerminated = false; + + debugLogA("CDiscordProto::WorkerThread: %s", "entering"); + + if (m_szAccessToken != nullptr) + RetrieveMyInfo(); // try to receive a response from server + else { + if (mir_wstrlen(m_wszEmail) == 0) { + ConnectionFailed(LOGINERR_BADUSERID); + return; + } + + ptrW wszPassword(getWStringA(DB_KEY_PASSWORD)); + if (wszPassword == nullptr) { + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return; + } + + JSONNode root; root << WCHAR_PARAM("email", m_wszEmail) << WCHAR_PARAM("password", wszPassword); + Push(new AsyncHttpRequest(this, REQUEST_POST, "/auth/login", &CDiscordProto::OnReceiveToken, &root)); + } + + while (true) { + WaitForSingleObject(m_evRequestsQueue, 1000); + if (m_bTerminated) + break; + + AsyncHttpRequest *pReq; + bool need_sleep = false; + while (true) { + { + mir_cslock lck(m_csHttpQueue); + if (m_arHttpQueue.getCount() == 0) + break; + + pReq = m_arHttpQueue[0]; + m_arHttpQueue.remove(0); + need_sleep = (m_arHttpQueue.getCount() > 1); + } + if (m_bTerminated) + break; + ExecuteRequest(pReq); + if (need_sleep) { + Sleep(330); + debugLogA("CDiscordProto::WorkerThread: %s", "need to sleep"); + } + } + } + + m_hWorkerThread = nullptr; + if (m_hAPIConnection) { + Netlib_CloseHandle(m_hAPIConnection); + m_hAPIConnection = nullptr; + } + + debugLogA("CDiscordProto::WorkerThread: %s", "leaving"); +} diff --git a/protocols/Discord/src/main.cpp b/protocols/Discord/src/main.cpp index c615047d00..98f0b120de 100644 --- a/protocols/Discord/src/main.cpp +++ b/protocols/Discord/src/main.cpp @@ -1,71 +1,71 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -CMPlugin g_plugin; - -///////////////////////////////////////////////////////////////////////////////////////// - -PLUGININFOEX pluginInfoEx = { - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - // {88928401-2CE8-4568-AAA7-226141870CBF} - { 0x88928401, 0x2ce8, 0x4568, { 0xaa, 0xa7, 0x22, 0x61, 0x41, 0x87, 0x0c, 0xbf } } -}; - -CMPlugin::CMPlugin() : - ACCPROTOPLUGIN("Discord", pluginInfoEx) -{ - SetUniqueId(DB_KEY_ID); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Interface information - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// -// Load - -IconItem g_iconList[] = -{ - { LPGEN("Main icon"), "main", IDI_MAIN }, - { LPGEN("Group chats"), "groupchat", IDI_GROUPCHAT }, - { LPGEN("Call"), "voicecall", IDI_VOICE_CALL }, - { LPGEN("Call ended"), "voiceend", IDI_VOICE_ENDED } -}; - -static int OnModulesLoaded(WPARAM, LPARAM) -{ - g_plugin.bVoiceService = ServiceExists(MS_VOICESERVICE_REGISTER); - return 0; -} - -int CMPlugin::Load() -{ - HookEvent(ME_SYSTEM_MODULESLOADED, &OnModulesLoaded); - - g_plugin.registerIcon("Protocols/Discord", g_iconList); - return 0; -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +CMPlugin g_plugin; + +///////////////////////////////////////////////////////////////////////////////////////// + +PLUGININFOEX pluginInfoEx = { + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {88928401-2CE8-4568-AAA7-226141870CBF} + { 0x88928401, 0x2ce8, 0x4568, { 0xaa, 0xa7, 0x22, 0x61, 0x41, 0x87, 0x0c, 0xbf } } +}; + +CMPlugin::CMPlugin() : + ACCPROTOPLUGIN("Discord", pluginInfoEx) +{ + SetUniqueId(DB_KEY_ID); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Interface information + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; + +///////////////////////////////////////////////////////////////////////////////////////// +// Load + +IconItem g_iconList[] = +{ + { LPGEN("Main icon"), "main", IDI_MAIN }, + { LPGEN("Group chats"), "groupchat", IDI_GROUPCHAT }, + { LPGEN("Call"), "voicecall", IDI_VOICE_CALL }, + { LPGEN("Call ended"), "voiceend", IDI_VOICE_ENDED } +}; + +static int OnModulesLoaded(WPARAM, LPARAM) +{ + g_plugin.bVoiceService = ServiceExists(MS_VOICESERVICE_REGISTER); + return 0; +} + +int CMPlugin::Load() +{ + HookEvent(ME_SYSTEM_MODULESLOADED, &OnModulesLoaded); + + g_plugin.registerIcon("Protocols/Discord", g_iconList); + return 0; +} diff --git a/protocols/Discord/src/menus.cpp b/protocols/Discord/src/menus.cpp index e88d91aa43..cc928221f3 100644 --- a/protocols/Discord/src/menus.cpp +++ b/protocols/Discord/src/menus.cpp @@ -1,172 +1,172 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -INT_PTR CDiscordProto::OnMenuCopyId(WPARAM hContact, LPARAM) -{ - CopyId(CMStringW(FORMAT, L"%s#%d", getMStringW(hContact, DB_KEY_NICK).c_str(), getDword(hContact, DB_KEY_DISCR))); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::OnMenuCreateChannel(WPARAM hContact, LPARAM) -{ - ENTER_STRING es = { m_szModuleName, "channel_name", TranslateT("Enter channel name"), nullptr, ESF_COMBO, 5 }; - if (EnterString(&es)) { - JSONNode roles(JSON_ARRAY); roles.set_name("permission_overwrites"); - JSONNode root; root << INT_PARAM("type", 0) << WCHAR_PARAM("name", es.ptszResult) << roles; - CMStringA szUrl(FORMAT, "/guilds/%lld/channels", getId(hContact, DB_KEY_CHANNELID)); - Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr, &root)); - mir_free(es.ptszResult); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::OnMenuJoinGuild(WPARAM, LPARAM) -{ - ENTER_STRING es = { m_szModuleName, "guild_name", TranslateT("Enter invitation code you received"), nullptr, ESF_COMBO, 5 }; - if (EnterString(&es)) { - CMStringA szUrl(FORMAT, "/invite/%S", es.ptszResult); - Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr)); - mir_free(es.ptszResult); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::OnMenuLeaveGuild(WPARAM hContact, LPARAM) -{ - if (IDYES == MessageBox(nullptr, TranslateT("Do you really want to leave the guild?"), m_tszUserName, MB_ICONQUESTION | MB_YESNOCANCEL)) { - CMStringA szUrl(FORMAT, "/users/@me/guilds/%lld", getId(hContact, DB_KEY_CHANNELID)); - Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, nullptr)); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::OnMenuLoadHistory(WPARAM hContact, LPARAM) -{ - auto *pUser = FindUser(getId(hContact, DB_KEY_ID)); - if (pUser) { - RetrieveHistory(pUser, MSG_AFTER, 0, 100); - delSetting(hContact, DB_KEY_LASTMSGID); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::OnMenuToggleSync(WPARAM hContact, LPARAM) -{ - bool bEnabled = !getBool(hContact, "EnableSync"); - setByte(hContact, "EnableSync", bEnabled); - - if (bEnabled) - if (auto *pGuild = FindGuild(getId(hContact, DB_KEY_CHANNELID))) - GatewaySendGuildInfo(pGuild); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CDiscordProto::OnMenuPrebuild(WPARAM hContact, LPARAM) -{ - // "Leave guild" menu item should be visible only for the guild contacts - bool bIsGuild = getByte(hContact, "ChatRoom") == 2; - Menu_ShowItem(m_hMenuLeaveGuild, bIsGuild); - Menu_ShowItem(m_hMenuCreateChannel, bIsGuild); - Menu_ShowItem(m_hMenuToggleSync, bIsGuild); - - if (!bIsGuild && getWord(hContact, "ApparentMode") != 0) - Menu_ShowItem(GetMenuItem(PROTO_MENU_REQ_AUTH), true); - - if (getByte(hContact, "EnableSync")) - Menu_ModifyItem(m_hMenuToggleSync, LPGENW("Disable sync"), Skin_GetIconHandle(SKINICON_CHAT_LEAVE)); - else - Menu_ModifyItem(m_hMenuToggleSync, LPGENW("Enable sync"), Skin_GetIconHandle(SKINICON_CHAT_JOIN)); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Protocol menu items - -void CDiscordProto::OnBuildProtoMenu() -{ - CMenuItem mi(&g_plugin); - mi.root = Menu_GetProtocolRoot(this); - mi.flags = CMIF_UNMOVABLE; - - mi.pszService = "/JoinGuild"; - CreateProtoService(mi.pszService, &CDiscordProto::OnMenuJoinGuild); - mi.name.a = LPGEN("Join guild"); - mi.position = 200001; - mi.hIcolibItem = g_iconList[1].hIcolib; - Menu_AddProtoMenuItem(&mi, m_szModuleName); - - mi.pszService = "/CopyId"; - CreateProtoService(mi.pszService, &CDiscordProto::OnMenuCopyId); - mi.name.a = LPGEN("Copy my Discord ID"); - mi.position = 200002; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_USERONLINE); - Menu_AddProtoMenuItem(&mi, m_szModuleName); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Contact menu items - -void CDiscordProto::InitMenus() -{ - CMenuItem mi(&g_plugin); - mi.pszService = "/LeaveGuild"; - CreateProtoService(mi.pszService, &CDiscordProto::OnMenuLeaveGuild); - SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8C); - mi.name.a = LPGEN("Leave guild"); - mi.position = -200001000; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_CHAT_LEAVE); - m_hMenuLeaveGuild = Menu_AddContactMenuItem(&mi, m_szModuleName); - - mi.pszService = "/CreateChannel"; - CreateProtoService(mi.pszService, &CDiscordProto::OnMenuCreateChannel); - SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8D); - mi.name.a = LPGEN("Create new channel"); - mi.position = -200001001; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_ADDCONTACT); - m_hMenuCreateChannel = Menu_AddContactMenuItem(&mi, m_szModuleName); - - SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8E); - mi.pszService = "/CopyId"; - mi.name.a = LPGEN("Copy ID"); - mi.position = -200001002; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_USERONLINE); - Menu_AddContactMenuItem(&mi, m_szModuleName); - - mi.pszService = "/ToggleSync"; - CreateProtoService(mi.pszService, &CDiscordProto::OnMenuToggleSync); - SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8F); - mi.name.a = LPGEN("Enable guild sync"); - mi.position = -200001003; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_CHAT_JOIN); - m_hMenuToggleSync = Menu_AddContactMenuItem(&mi, m_szModuleName); - - HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CDiscordProto::OnMenuPrebuild); -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +INT_PTR CDiscordProto::OnMenuCopyId(WPARAM hContact, LPARAM) +{ + CopyId(CMStringW(FORMAT, L"%s#%d", getMStringW(hContact, DB_KEY_NICK).c_str(), getDword(hContact, DB_KEY_DISCR))); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuCreateChannel(WPARAM hContact, LPARAM) +{ + ENTER_STRING es = { m_szModuleName, "channel_name", TranslateT("Enter channel name"), nullptr, ESF_COMBO, 5 }; + if (EnterString(&es)) { + JSONNode roles(JSON_ARRAY); roles.set_name("permission_overwrites"); + JSONNode root; root << INT_PARAM("type", 0) << WCHAR_PARAM("name", es.ptszResult) << roles; + CMStringA szUrl(FORMAT, "/guilds/%lld/channels", getId(hContact, DB_KEY_CHANNELID)); + Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr, &root)); + mir_free(es.ptszResult); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuJoinGuild(WPARAM, LPARAM) +{ + ENTER_STRING es = { m_szModuleName, "guild_name", TranslateT("Enter invitation code you received"), nullptr, ESF_COMBO, 5 }; + if (EnterString(&es)) { + CMStringA szUrl(FORMAT, "/invite/%S", es.ptszResult); + Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr)); + mir_free(es.ptszResult); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuLeaveGuild(WPARAM hContact, LPARAM) +{ + if (IDYES == MessageBox(nullptr, TranslateT("Do you really want to leave the guild?"), m_tszUserName, MB_ICONQUESTION | MB_YESNOCANCEL)) { + CMStringA szUrl(FORMAT, "/users/@me/guilds/%lld", getId(hContact, DB_KEY_CHANNELID)); + Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, nullptr)); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuLoadHistory(WPARAM hContact, LPARAM) +{ + auto *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser) { + RetrieveHistory(pUser, MSG_AFTER, 0, 100); + delSetting(hContact, DB_KEY_LASTMSGID); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuToggleSync(WPARAM hContact, LPARAM) +{ + bool bEnabled = !getBool(hContact, "EnableSync"); + setByte(hContact, "EnableSync", bEnabled); + + if (bEnabled) + if (auto *pGuild = FindGuild(getId(hContact, DB_KEY_CHANNELID))) + GatewaySendGuildInfo(pGuild); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CDiscordProto::OnMenuPrebuild(WPARAM hContact, LPARAM) +{ + // "Leave guild" menu item should be visible only for the guild contacts + bool bIsGuild = getByte(hContact, "ChatRoom") == 2; + Menu_ShowItem(m_hMenuLeaveGuild, bIsGuild); + Menu_ShowItem(m_hMenuCreateChannel, bIsGuild); + Menu_ShowItem(m_hMenuToggleSync, bIsGuild); + + if (!bIsGuild && getWord(hContact, "ApparentMode") != 0) + Menu_ShowItem(GetMenuItem(PROTO_MENU_REQ_AUTH), true); + + if (getByte(hContact, "EnableSync")) + Menu_ModifyItem(m_hMenuToggleSync, LPGENW("Disable sync"), Skin_GetIconHandle(SKINICON_CHAT_LEAVE)); + else + Menu_ModifyItem(m_hMenuToggleSync, LPGENW("Enable sync"), Skin_GetIconHandle(SKINICON_CHAT_JOIN)); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Protocol menu items + +void CDiscordProto::OnBuildProtoMenu() +{ + CMenuItem mi(&g_plugin); + mi.root = Menu_GetProtocolRoot(this); + mi.flags = CMIF_UNMOVABLE; + + mi.pszService = "/JoinGuild"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuJoinGuild); + mi.name.a = LPGEN("Join guild"); + mi.position = 200001; + mi.hIcolibItem = g_iconList[1].hIcolib; + Menu_AddProtoMenuItem(&mi, m_szModuleName); + + mi.pszService = "/CopyId"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuCopyId); + mi.name.a = LPGEN("Copy my Discord ID"); + mi.position = 200002; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_USERONLINE); + Menu_AddProtoMenuItem(&mi, m_szModuleName); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Contact menu items + +void CDiscordProto::InitMenus() +{ + CMenuItem mi(&g_plugin); + mi.pszService = "/LeaveGuild"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuLeaveGuild); + SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8C); + mi.name.a = LPGEN("Leave guild"); + mi.position = -200001000; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_CHAT_LEAVE); + m_hMenuLeaveGuild = Menu_AddContactMenuItem(&mi, m_szModuleName); + + mi.pszService = "/CreateChannel"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuCreateChannel); + SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8D); + mi.name.a = LPGEN("Create new channel"); + mi.position = -200001001; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_ADDCONTACT); + m_hMenuCreateChannel = Menu_AddContactMenuItem(&mi, m_szModuleName); + + SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8E); + mi.pszService = "/CopyId"; + mi.name.a = LPGEN("Copy ID"); + mi.position = -200001002; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_USERONLINE); + Menu_AddContactMenuItem(&mi, m_szModuleName); + + mi.pszService = "/ToggleSync"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuToggleSync); + SET_UID(mi, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8F); + mi.name.a = LPGEN("Enable guild sync"); + mi.position = -200001003; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_CHAT_JOIN); + m_hMenuToggleSync = Menu_AddContactMenuItem(&mi, m_szModuleName); + + HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CDiscordProto::OnMenuPrebuild); +} diff --git a/protocols/Discord/src/options.cpp b/protocols/Discord/src/options.cpp index 86f3519df8..3ced623311 100644 --- a/protocols/Discord/src/options.cpp +++ b/protocols/Discord/src/options.cpp @@ -1,100 +1,100 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -class CDiscardAccountOptions : public CProtoDlgBase -{ - CCtrlCheck chkUseChats, chkHideChats, chkUseGroups, chkDeleteMsgs; - CCtrlEdit m_edGroup, m_edUserName, m_edPassword; - ptrW m_wszOldGroup; - -public: - CDiscardAccountOptions(CDiscordProto *ppro, int iDlgID, bool bFullDlg) : - CProtoDlgBase(ppro, iDlgID), - m_edGroup(this, IDC_GROUP), - m_edUserName(this, IDC_USERNAME), - m_edPassword(this, IDC_PASSWORD), - chkUseChats(this, IDC_USEGUILDS), - chkHideChats(this, IDC_HIDECHATS), - chkUseGroups(this, IDC_USEGROUPS), - chkDeleteMsgs(this, IDC_DELETE_MSGS), - m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) - { - CreateLink(m_edGroup, ppro->m_wszDefaultGroup); - CreateLink(m_edUserName, ppro->m_wszEmail); - if (bFullDlg) { - CreateLink(chkUseChats, ppro->m_bUseGroupchats); - CreateLink(chkHideChats, ppro->m_bHideGroupchats); - CreateLink(chkUseGroups, ppro->m_bUseGuildGroups); - CreateLink(chkDeleteMsgs, ppro->m_bSyncDeleteMsgs); - - chkUseChats.OnChange = Callback(this, &CDiscardAccountOptions::onChange_GroupChats); - } - } - - bool OnInitDialog() override - { - ptrW buf(m_proto->getWStringA(DB_KEY_PASSWORD)); - if (buf) - m_edPassword.SetText(buf); - return true; - } - - bool OnApply() override - { - if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) - Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); - - ptrW buf(m_edPassword.GetText()); - m_proto->setWString(DB_KEY_PASSWORD, buf); - return true; - } - - void onChange_GroupChats(CCtrlCheck*) - { - bool bEnabled = chkUseChats.GetState(); - chkHideChats.Enable(bEnabled); - chkUseGroups.Enable(bEnabled); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) -{ - CDiscardAccountOptions *pDlg = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCMGR, false); - pDlg->SetParent((HWND)hwndParent); - pDlg->Create(); - return (INT_PTR)pDlg->GetHwnd(); -} - -int CDiscordProto::OnOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szTitle.w = m_tszUserName; - odp.flags = ODPF_UNICODE; - odp.szGroup.w = LPGENW("Network"); - - odp.position = 1; - odp.szTab.w = LPGENW("Account"); - odp.pDialog = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCOUNT, true); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// + +class CDiscardAccountOptions : public CProtoDlgBase +{ + CCtrlCheck chkUseChats, chkHideChats, chkUseGroups, chkDeleteMsgs; + CCtrlEdit m_edGroup, m_edUserName, m_edPassword; + ptrW m_wszOldGroup; + +public: + CDiscardAccountOptions(CDiscordProto *ppro, int iDlgID, bool bFullDlg) : + CProtoDlgBase(ppro, iDlgID), + m_edGroup(this, IDC_GROUP), + m_edUserName(this, IDC_USERNAME), + m_edPassword(this, IDC_PASSWORD), + chkUseChats(this, IDC_USEGUILDS), + chkHideChats(this, IDC_HIDECHATS), + chkUseGroups(this, IDC_USEGROUPS), + chkDeleteMsgs(this, IDC_DELETE_MSGS), + m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) + { + CreateLink(m_edGroup, ppro->m_wszDefaultGroup); + CreateLink(m_edUserName, ppro->m_wszEmail); + if (bFullDlg) { + CreateLink(chkUseChats, ppro->m_bUseGroupchats); + CreateLink(chkHideChats, ppro->m_bHideGroupchats); + CreateLink(chkUseGroups, ppro->m_bUseGuildGroups); + CreateLink(chkDeleteMsgs, ppro->m_bSyncDeleteMsgs); + + chkUseChats.OnChange = Callback(this, &CDiscardAccountOptions::onChange_GroupChats); + } + } + + bool OnInitDialog() override + { + ptrW buf(m_proto->getWStringA(DB_KEY_PASSWORD)); + if (buf) + m_edPassword.SetText(buf); + return true; + } + + bool OnApply() override + { + if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) + Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); + + ptrW buf(m_edPassword.GetText()); + m_proto->setWString(DB_KEY_PASSWORD, buf); + return true; + } + + void onChange_GroupChats(CCtrlCheck*) + { + bool bEnabled = chkUseChats.GetState(); + chkHideChats.Enable(bEnabled); + chkUseGroups.Enable(bEnabled); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) +{ + CDiscardAccountOptions *pDlg = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCMGR, false); + pDlg->SetParent((HWND)hwndParent); + pDlg->Create(); + return (INT_PTR)pDlg->GetHwnd(); +} + +int CDiscordProto::OnOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.szTitle.w = m_tszUserName; + odp.flags = ODPF_UNICODE; + odp.szGroup.w = LPGENW("Network"); + + odp.position = 1; + odp.szTab.w = LPGENW("Account"); + odp.pDialog = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCOUNT, true); + g_plugin.addOptions(wParam, &odp); + return 0; +} diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp index 972c6ec312..2bd02f704d 100644 --- a/protocols/Discord/src/proto.cpp +++ b/protocols/Discord/src/proto.cpp @@ -1,768 +1,768 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -static int compareMessages(const COwnMessage *p1, const COwnMessage *p2) -{ - return compareInt64(p1->nonce, p2->nonce); -} - -static int compareRequests(const AsyncHttpRequest *p1, const AsyncHttpRequest *p2) -{ - return p1->m_iReqNum - p2->m_iReqNum; -} - -int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2) -{ - return compareInt64(p1->id, p2->id); -} - -static int compareGuilds(const CDiscordGuild *p1, const CDiscordGuild *p2) -{ - return compareInt64(p1->id, p2->id); -} - -CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : - PROTO(proto_name, username), - m_impl(*this), - m_arHttpQueue(10, compareRequests), - m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr)), - arUsers(10, compareUsers), - arGuilds(1, compareGuilds), - arMarkReadQueue(1, compareUsers), - arOwnMessages(1, compareMessages), - arVoiceCalls(1), - - m_wszEmail(this, "Email", L""), - m_wszDefaultGroup(this, "GroupName", DB_KEYVAL_GROUP), - m_bUseGroupchats(this, "UseGroupChats", true), - m_bHideGroupchats(this, "HideChats", true), - m_bUseGuildGroups(this, "UseGuildGroups", false), - m_bSyncDeleteMsgs(this, "DeleteServerMsgs", true) -{ - // Services - CreateProtoService(PS_CREATEACCMGRUI, &CDiscordProto::SvcCreateAccMgrUI); - - CreateProtoService(PS_GETAVATARINFO, &CDiscordProto::GetAvatarInfo); - CreateProtoService(PS_GETAVATARCAPS, &CDiscordProto::GetAvatarCaps); - CreateProtoService(PS_GETMYAVATAR, &CDiscordProto::GetMyAvatar); - CreateProtoService(PS_SETMYAVATAR, &CDiscordProto::SetMyAvatar); - - CreateProtoService(PS_MENU_REQAUTH, &CDiscordProto::RequestFriendship); - CreateProtoService(PS_MENU_LOADHISTORY, &CDiscordProto::OnMenuLoadHistory); - - CreateProtoService(PS_VOICE_CAPS, &CDiscordProto::VoiceCaps); - - // Events - HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit); - HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CDiscordProto::OnDbEventRead); - HookProtoEvent(ME_PROTO_ACCLISTCHANGED, &CDiscordProto::OnAccountChanged); - - HookProtoEvent(PE_VOICE_CALL_STATE, &CDiscordProto::OnVoiceState); - - // database - db_set_resident(m_szModuleName, "XStatusMsg"); - - // custom events - DBEVENTTYPEDESCR dbEventType = {}; - dbEventType.module = m_szModuleName; - dbEventType.flags = DETF_HISTORY | DETF_MSGWINDOW; - - dbEventType.eventType = EVENT_INCOMING_CALL; - dbEventType.descr = Translate("Incoming call"); - dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_CALL); - DbEvent_RegisterType(&dbEventType); - - dbEventType.eventType = EVENT_CALL_FINISHED; - dbEventType.descr = Translate("Call ended"); - dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_ENDED); - DbEvent_RegisterType(&dbEventType); - - // Groupchat initialization - GCREGISTER gcr = {}; - gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR; - gcr.ptszDispName = m_tszUserName; - gcr.pszModule = m_szModuleName; - Chat_Register(&gcr); - - // Network initialization - CMStringW descr; - NETLIBUSER nlu = {}; - - nlu.szSettingsModule = m_szModuleName; - nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; - descr.Format(TranslateT("%s server connection"), m_tszUserName); - nlu.szDescriptiveName.w = descr.GetBuffer(); - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - CMStringA module(FORMAT, "%s.Gateway", m_szModuleName); - nlu.szSettingsModule = module.GetBuffer(); - nlu.flags = NUF_OUTGOING | NUF_UNICODE; - descr.Format(TranslateT("%s gateway connection"), m_tszUserName); - nlu.szDescriptiveName.w = descr.GetBuffer(); - m_hGatewayNetlibUser = Netlib_RegisterUser(&nlu); -} - -CDiscordProto::~CDiscordProto() -{ - debugLogA("CDiscordProto::~CDiscordProto"); - - for (auto &msg : m_wszStatusMsg) - mir_free(msg); - - arUsers.destroy(); - - m_arHttpQueue.destroy(); - ::CloseHandle(m_evRequestsQueue); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::OnModulesLoaded() -{ - std::vector lostIds; - - // Fill users list - for (auto &hContact : AccContacts()) { - CDiscordUser *pNew = new CDiscordUser(getId(hContact, DB_KEY_ID)); - pNew->hContact = hContact; - pNew->lastMsgId = getId(hContact, DB_KEY_LASTMSGID); - pNew->wszUsername = ptrW(getWStringA(hContact, DB_KEY_NICK)); - pNew->iDiscriminator = getDword(hContact, DB_KEY_DISCR); - - // set EnableSync = 1 by default for all existing guilds - switch (getByte(hContact, "ChatRoom")) { - case 2: // guild - delSetting(hContact, DB_KEY_CHANNELID); - if (getDword(hContact, "EnableSync", -1) == -1) - setDword(hContact, "EnableSync", 1); - break; - - case 1: // group chat - pNew->channelId = getId(hContact, DB_KEY_CHANNELID); - if (!pNew->channelId) { - lostIds.push_back(hContact); - delete pNew; - continue; - } - break; - - default: - pNew->channelId = getId(hContact, DB_KEY_CHANNELID); - break; - } - arUsers.insert(pNew); - } - - for (auto &hContact: lostIds) - db_delete_contact(hContact); - - // Clist - Clist_GroupCreate(0, m_wszDefaultGroup); - - HookProtoEvent(ME_GC_EVENT, &CDiscordProto::GroupchatEventHook); - HookProtoEvent(ME_GC_BUILDMENU, &CDiscordProto::GroupchatMenuHook); - - InitMenus(); - - // Voice support - if (g_plugin.bVoiceService) { - VOICE_MODULE voice = {}; - voice.cbSize = sizeof(voice); - voice.name = m_szModuleName; - voice.description = TranslateT("Discord voice call"); - voice.icon = m_hProtoIcon; - voice.flags = VOICE_CAPS_CALL_CONTACT | VOICE_CAPS_VOICE; - CallService(MS_VOICESERVICE_REGISTER, (WPARAM)&voice, 0); - } -} - -void CDiscordProto::OnShutdown() -{ - debugLogA("CDiscordProto::OnPreShutdown"); - - m_bTerminated = true; - SetEvent(m_evRequestsQueue); - - for (auto &it : arGuilds) - it->SaveToFile(); - - if (m_hGatewayConnection) - Netlib_Shutdown(m_hGatewayConnection); - - if (g_plugin.bVoiceService) - CallService(MS_VOICESERVICE_UNREGISTER, (WPARAM)m_szModuleName, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::GetCaps(int type, MCONTACT) -{ - switch (type) { - case PFLAGNUM_1: - return PF1_IM | PF1_MODEMSG | PF1_MODEMSGRECV | PF1_SERVERCLIST | PF1_BASICSEARCH | PF1_EXTSEARCH | PF1_ADDSEARCHRES | PF1_FILESEND; - case PFLAGNUM_2: - return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_INVISIBLE; - case PFLAGNUM_3: - return PF2_ONLINE | PF2_LONGAWAY | PF2_HEAVYDND | PF2_INVISIBLE; - case PFLAGNUM_4: - return PF4_FORCEAUTH | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_SUPPORTIDLE | PF4_AVATARS | PF4_IMSENDOFFLINE | PF4_SERVERMSGID | PF4_OFFLINEFILES; - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)TranslateT("User ID"); - } - return 0; -} - -int CDiscordProto::SetStatus(int iNewStatus) -{ - debugLogA("CDiscordProto::SetStatus iNewStatus = %d, m_iStatus = %d, m_iDesiredStatus = %d m_hWorkerThread = %p", iNewStatus, m_iStatus, m_iDesiredStatus, m_hWorkerThread); - - if (iNewStatus == m_iStatus) - return 0; - - m_iDesiredStatus = iNewStatus; - int iOldStatus = m_iStatus; - - // go offline - if (iNewStatus == ID_STATUS_OFFLINE) { - if (m_bOnline) { - SetServerStatus(ID_STATUS_OFFLINE); - ShutdownSession(); - } - m_iStatus = m_iDesiredStatus; - setAllContactStatuses(ID_STATUS_OFFLINE, false); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - } - // not logged in? come on - else if (m_hWorkerThread == nullptr && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - m_hWorkerThread = ForkThreadEx(&CDiscordProto::ServerThread, nullptr, nullptr); - } - else if (m_bOnline) { - debugLogA("setting server online status to %d", iNewStatus); - SetServerStatus(iNewStatus); - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM) -{ - switch (msg) { - case WM_INITDIALOG: - TranslateDialogDefault(hwndDlg); - SetFocus(GetDlgItem(hwndDlg, IDC_NICK)); - return TRUE; - - case WM_COMMAND: - if (HIWORD(wParam) == EN_SETFOCUS) - PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg); - } - return FALSE; -} - -HWND CDiscordProto::CreateExtendedSearchUI(HWND hwndParent) -{ - if (hwndParent) - return CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_EXTSEARCH), hwndParent, AdvancedSearchDlgProc, 0); - - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::SearchThread(void *param) -{ - Sleep(100); - - PROTOSEARCHRESULT psr = { 0 }; - psr.cbSize = sizeof(psr); - psr.flags = PSR_UNICODE; - psr.nick.w = (wchar_t*)param; - psr.firstName.w = L""; - psr.lastName.w = L""; - psr.id.w = L""; - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)1, (LPARAM)&psr); - - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0); - mir_free(param); -} - -HWND CDiscordProto::SearchAdvanced(HWND hwndDlg) -{ - if (!m_bOnline || !IsWindow(hwndDlg)) - return nullptr; - - wchar_t wszNick[200]; - GetDlgItemTextW(hwndDlg, IDC_NICK, wszNick, _countof(wszNick)); - if (wszNick[0] == 0) // empty string? reject - return nullptr; - - wchar_t *p = wcschr(wszNick, '#'); - if (p == nullptr) // wrong user id - return nullptr; - - ForkThread(&CDiscordProto::SearchThread, mir_wstrdup(wszNick)); - return (HWND)1; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Basic search - by SnowFlake - -void CDiscordProto::OnReceiveUserinfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (!root) { - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)1); - return; - } - - auto &data = root.data(); - CMStringW wszUserId(data["username"].as_mstring() + L"#" + data["discriminator"].as_mstring()); - ForkThread(&CDiscordProto::SearchThread, wszUserId.Detach()); -} - -HANDLE CDiscordProto::SearchBasic(const wchar_t *wszId) -{ - if (!m_bOnline) - return nullptr; - - CMStringA szUrl = "/users/"; - szUrl.AppendFormat(ptrA(mir_utf8encodeW(wszId))); - Push(new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveUserinfo)); - return (HANDLE)1; // Success -} - -//////////////////////////////////////////////////////////////////////////////////////// -// Authorization - -int CDiscordProto::AuthRequest(MCONTACT hContact, const wchar_t*) -{ - ptrW wszUsername(getWStringA(hContact, DB_KEY_NICK)); - int iDiscriminator(getDword(hContact, DB_KEY_DISCR, -1)); - if (wszUsername == nullptr || iDiscriminator == -1) - return 1; // error - - JSONNode root; root << WCHAR_PARAM("username", wszUsername) << INT_PARAM("discriminator", iDiscriminator); - Push(new AsyncHttpRequest(this, REQUEST_POST, "/users/@me/relationships", nullptr, &root)); - return 0; -} - -int CDiscordProto::AuthRecv(MCONTACT, PROTORECVEVENT *pre) -{ - return Proto_AuthRecv(m_szModuleName, pre); -} - -int CDiscordProto::Authorize(MEVENT hDbEvent) -{ - DB::EventInfo dbei; - dbei.cbBlob = -1; - if (db_event_get(hDbEvent, &dbei)) return 1; - if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return 1; - if (mir_strcmp(dbei.szModule, m_szModuleName)) return 1; - - JSONNode root; - MCONTACT hContact = DbGetAuthEventContact(&dbei); - CMStringA szUrl(FORMAT, "/users/@me/relationships/%lld", getId(hContact, DB_KEY_ID)); - Push(new AsyncHttpRequest(this, REQUEST_PUT, szUrl, nullptr, &root)); - return 0; -} - -int CDiscordProto::AuthDeny(MEVENT hDbEvent, const wchar_t*) -{ - DB::EventInfo dbei; - dbei.cbBlob = -1; - if (db_event_get(hDbEvent, &dbei)) return 1; - if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return 1; - if (mir_strcmp(dbei.szModule, m_szModuleName)) return 1; - - MCONTACT hContact = DbGetAuthEventContact(&dbei); - RemoveFriend(getId(hContact, DB_KEY_ID)); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// - -MCONTACT CDiscordProto::AddToList(int flags, PROTOSEARCHRESULT *psr) -{ - if (mir_wstrlen(psr->nick.w) == 0) - return 0; - - wchar_t *p = wcschr(psr->nick.w, '#'); - if (p == nullptr) - return 0; - - MCONTACT hContact = db_add_contact(); - Proto_AddToContact(hContact, m_szModuleName); - if (flags & PALF_TEMPORARY) - Contact::RemoveFromList(hContact); - - *p = 0; - CDiscordUser *pUser = new CDiscordUser(0); - pUser->hContact = hContact; - pUser->wszUsername = psr->nick.w; - pUser->iDiscriminator = _wtoi(p + 1); - *p = '#'; - - if (mir_wstrlen(psr->id.w)) { - pUser->id = _wtoi64(psr->id.w); - setId(hContact, DB_KEY_ID, pUser->id); - } - - Clist_SetGroup(hContact, m_wszDefaultGroup); - setWString(hContact, DB_KEY_NICK, pUser->wszUsername); - setDword(hContact, DB_KEY_DISCR, pUser->iDiscriminator); - arUsers.insert(pUser); - - return hContact; -} - -MCONTACT CDiscordProto::AddToListByEvent(int flags, int, MEVENT hDbEvent) -{ - DB::EventInfo dbei; - dbei.cbBlob = -1; - if (db_event_get(hDbEvent, &dbei)) - return 0; - if (mir_strcmp(dbei.szModule, m_szModuleName)) - return 0; - if (dbei.eventType != EVENTTYPE_AUTHREQUEST) - return 0; - - DB::AUTH_BLOB blob(dbei.pBlob); - if (flags & PALF_TEMPORARY) - Contact::RemoveFromList(blob.get_contact()); - else - Contact::PutOnList(blob.get_contact()); - return blob.get_contact(); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// SendMsg - -void CDiscordProto::OnSendMsg(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - JsonReply root(pReply); - if (!root) { - int iReqNum = -1; - for (auto &it : arOwnMessages) - if (it->reqId == pReq->m_iReqNum) { - iReqNum = it->reqId; - arOwnMessages.removeItem(&it); - break; - } - - if (iReqNum != -1) { - CMStringW wszErrorMsg(root.data()["message"].as_mstring()); - if (wszErrorMsg.IsEmpty()) - wszErrorMsg = TranslateT("Message send failed"); - ProtoBroadcastAck(pReq->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)iReqNum, (LPARAM)wszErrorMsg.c_str()); - } - } -} - -int CDiscordProto::SendMsg(MCONTACT hContact, int /*flags*/, const char *pszSrc) -{ - if (!m_bOnline) { - ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)TranslateT("Protocol is offline or user isn't authorized yet")); - return 1; - } - - ptrW wszText(mir_utf8decodeW(pszSrc)); - if (wszText == nullptr) - return 0; - - CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); - if (pUser == nullptr || pUser->id == 0) - return 0; - - // no channel - we need to create one - if (pUser->channelId == 0) { - JSONNode list(JSON_ARRAY); list.set_name("recipients"); list << SINT64_PARAM("", pUser->id); - JSONNode body; body << list; - CMStringA szUrl(FORMAT, "/users/%lld/channels", m_ownId); - - // theoretically we get the same data from the gateway thread, but there could be a delay - // so we bind data analysis to the http packet reply - mir_cslock lck(m_csHttpQueue); - ExecuteRequest(new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveCreateChannel, &body)); - if (pUser->channelId == 0) - return 0; - } - - // we generate a random 64-bit integer and pass it to the server - // to distinguish our own messages from these generated by another clients - SnowFlake nonce; Utils_GetRandom(&nonce, sizeof(nonce)); nonce = abs(nonce); - JSONNode body; body << WCHAR_PARAM("content", wszText) << SINT64_PARAM("nonce", nonce); - - CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId); - AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnSendMsg, &body); - pReq->hContact = hContact; - arOwnMessages.insert(new COwnMessage(nonce, pReq->m_iReqNum)); - Push(pReq); - return pReq->m_iReqNum; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void __cdecl CDiscordProto::GetAwayMsgThread(void *param) -{ - Thread_SetName("Jabber: GetAwayMsgThread"); - - auto *pUser = (CDiscordUser *)param; - if (pUser == nullptr) - return; - - if (pUser->wszTopic.IsEmpty()) - ProtoBroadcastAck(pUser->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, 0); - else - ProtoBroadcastAck(pUser->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)pUser->wszTopic.c_str()); -} - -HANDLE CDiscordProto::GetAwayMsg(MCONTACT hContact) -{ - ForkThread(&CDiscordProto::GetAwayMsgThread, FindUser(getId(hContact, DB_KEY_ID))); - return (HANDLE)1; -} - -int CDiscordProto::SetAwayMsg(int iStatus, const wchar_t *msg) -{ - if (iStatus < ID_STATUS_MIN || iStatus > ID_STATUS_MAX) - return 0; - - wchar_t *&pwszMessage = m_wszStatusMsg[iStatus - ID_STATUS_MIN]; - if (!mir_wstrcmp(msg, pwszMessage)) - return 0; - - replaceStrW(pwszMessage, msg); - - if (m_bOnline) { - JSONNode status; status.set_name("custom_status"); status << WCHAR_PARAM("text", (msg) ? msg : L""); - JSONNode root; root << status; - Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me/settings", nullptr, &root)); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CDiscordProto::UserIsTyping(MCONTACT hContact, int type) -{ - if (type == PROTOTYPE_SELFTYPING_ON) { - CMStringA szUrl(FORMAT, "/channels/%lld/typing", getId(hContact, DB_KEY_CHANNELID)); - Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr)); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::OnReceiveMarkRead(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - JsonReply root(pReply); - if (root) - SaveToken(root.data()); -} - -void CDiscordProto::SendMarkRead() -{ - mir_cslock lck(csMarkReadQueue); - while (arMarkReadQueue.getCount()) { - CDiscordUser *pUser = arMarkReadQueue[0]; - JSONNode payload; payload << CHAR_PARAM("token", m_szTempToken); - CMStringA szUrl(FORMAT, "/channels/%lld/messages/%lld/ack", pUser->channelId, pUser->lastMsgId); - auto *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveMarkRead, &payload); - Push(pReq); - arMarkReadQueue.remove(0); - } -} - -int CDiscordProto::OnDbEventRead(WPARAM, LPARAM hDbEvent) -{ - MCONTACT hContact = db_event_getContact(hDbEvent); - if (!hContact) - return 0; - - // filter out only events of my protocol - const char *szProto = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(szProto, m_szModuleName)) - return 0; - - if (m_bOnline) { - m_impl.m_markRead.Start(200); - - CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); - if (pUser != nullptr) { - mir_cslock lck(csMarkReadQueue); - if (arMarkReadQueue.indexOf(pUser) == -1) - arMarkReadQueue.insert(pUser); - } - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CDiscordProto::OnAccountChanged(WPARAM iAction, LPARAM lParam) -{ - if (iAction == PRAC_ADDED) { - PROTOACCOUNT *pa = (PROTOACCOUNT*)lParam; - if (pa && pa->ppro == this) { - m_bUseGroupchats = false; - m_bUseGuildGroups = true; - } - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::OnContactDeleted(MCONTACT hContact) -{ - CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); - if (pUser == nullptr || !m_bOnline) - return; - - if (pUser->channelId) - Push(new AsyncHttpRequest(this, REQUEST_DELETE, CMStringA(FORMAT, "/channels/%lld", pUser->channelId), nullptr)); - - if (pUser->id) - RemoveFriend(pUser->id); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CDiscordProto::RequestFriendship(WPARAM hContact, LPARAM) -{ - AuthRequest(hContact, 0); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -struct SendFileThreadParam -{ - MCONTACT hContact; - CMStringW wszDescr, wszFileName; - - SendFileThreadParam(MCONTACT _p1, LPCWSTR _p2, LPCWSTR _p3) : - hContact(_p1), - wszFileName(_p2), - wszDescr(_p3) - {} -}; - -void CDiscordProto::SendFileThread(void *param) -{ - SendFileThreadParam *p = (SendFileThreadParam*)param; - - FILE *in = _wfopen(p->wszFileName, L"rb"); - if (in == nullptr) { - debugLogA("cannot open file %S for reading", p->wszFileName.c_str()); - LBL_Error: - ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, param); - delete p; - return; - } - - ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, param); - - char szRandom[16], szRandomText[33]; - Utils_GetRandom(szRandom, _countof(szRandom)); - bin2hex(szRandom, _countof(szRandom), szRandomText); - CMStringA szBoundary(FORMAT, "----Boundary%s", szRandomText); - - CMStringA szUrl(FORMAT, "/channels/%lld/messages", getId(p->hContact, DB_KEY_CHANNELID)); - AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveFile); - pReq->AddHeader("Content-Type", CMStringA("multipart/form-data; boundary=" + szBoundary)); - pReq->AddHeader("Accept", "*/*"); - - szBoundary.Insert(0, "--"); - - CMStringA szBody; - szBody.Append(szBoundary + "\r\n"); - szBody.Append("Content-Disposition: form-data; name=\"content\"\r\n\r\n"); - szBody.Append(ptrA(mir_utf8encodeW(p->wszDescr))); - szBody.Append("\r\n"); - - szBody.Append(szBoundary + "\r\n"); - szBody.Append("Content-Disposition: form-data; name=\"tts\"\r\n\r\nfalse\r\n"); - - wchar_t *pFileName = wcsrchr(p->wszFileName.GetBuffer(), '\\'); - if (pFileName != nullptr) - pFileName++; - else - pFileName = p->wszFileName.GetBuffer(); - - szBody.Append(szBoundary + "\r\n"); - szBody.AppendFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", ptrA(mir_utf8encodeW(pFileName)).get()); - szBody.AppendFormat("Content-Type: %S\r\n", ProtoGetAvatarMimeType(ProtoGetAvatarFileFormat(p->wszFileName))); - szBody.Append("\r\n"); - - size_t cbBytes = filelength(fileno(in)); - - szBoundary.Insert(0, "\r\n"); - szBoundary.Append("--\r\n"); - pReq->dataLength = int(szBody.GetLength() + szBoundary.GetLength() + cbBytes); - pReq->pData = (char*)mir_alloc(pReq->dataLength+1); - memcpy(pReq->pData, szBody.c_str(), szBody.GetLength()); - size_t cbRead = fread(pReq->pData + szBody.GetLength(), 1, cbBytes, in); - fclose(in); - if (cbBytes != cbRead) { - debugLogA("cannot read file %S: %d bytes read instead of %d", p->wszFileName.c_str(), cbRead, cbBytes); - delete pReq; - goto LBL_Error; - } - - memcpy(pReq->pData + szBody.GetLength() + cbBytes, szBoundary, szBoundary.GetLength()); - pReq->pUserInfo = p; - Push(pReq); - - ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, param); -} - -void CDiscordProto::OnReceiveFile(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - SendFileThreadParam *p = (SendFileThreadParam*)pReq->pUserInfo; - if (pReply->resultCode != 200) { - ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, p); - debugLogA("CDiscordProto::SendFile failed: %d", pReply->resultCode); - } - else { - ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, p); - debugLogA("CDiscordProto::SendFile succeeded"); - } - - delete p; -} - -HANDLE CDiscordProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) -{ - SnowFlake id = getId(hContact, DB_KEY_CHANNELID); - if (id == 0) - return nullptr; - - // we don't wanna block the main thread, right? - SendFileThreadParam *param = new SendFileThreadParam(hContact, ppszFiles[0], szDescription); - ForkThread(&CDiscordProto::SendFileThread, param); - return param; -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +static int compareMessages(const COwnMessage *p1, const COwnMessage *p2) +{ + return compareInt64(p1->nonce, p2->nonce); +} + +static int compareRequests(const AsyncHttpRequest *p1, const AsyncHttpRequest *p2) +{ + return p1->m_iReqNum - p2->m_iReqNum; +} + +int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2) +{ + return compareInt64(p1->id, p2->id); +} + +static int compareGuilds(const CDiscordGuild *p1, const CDiscordGuild *p2) +{ + return compareInt64(p1->id, p2->id); +} + +CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : + PROTO(proto_name, username), + m_impl(*this), + m_arHttpQueue(10, compareRequests), + m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr)), + arUsers(10, compareUsers), + arGuilds(1, compareGuilds), + arMarkReadQueue(1, compareUsers), + arOwnMessages(1, compareMessages), + arVoiceCalls(1), + + m_wszEmail(this, "Email", L""), + m_wszDefaultGroup(this, "GroupName", DB_KEYVAL_GROUP), + m_bUseGroupchats(this, "UseGroupChats", true), + m_bHideGroupchats(this, "HideChats", true), + m_bUseGuildGroups(this, "UseGuildGroups", false), + m_bSyncDeleteMsgs(this, "DeleteServerMsgs", true) +{ + // Services + CreateProtoService(PS_CREATEACCMGRUI, &CDiscordProto::SvcCreateAccMgrUI); + + CreateProtoService(PS_GETAVATARINFO, &CDiscordProto::GetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &CDiscordProto::GetAvatarCaps); + CreateProtoService(PS_GETMYAVATAR, &CDiscordProto::GetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &CDiscordProto::SetMyAvatar); + + CreateProtoService(PS_MENU_REQAUTH, &CDiscordProto::RequestFriendship); + CreateProtoService(PS_MENU_LOADHISTORY, &CDiscordProto::OnMenuLoadHistory); + + CreateProtoService(PS_VOICE_CAPS, &CDiscordProto::VoiceCaps); + + // Events + HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit); + HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CDiscordProto::OnDbEventRead); + HookProtoEvent(ME_PROTO_ACCLISTCHANGED, &CDiscordProto::OnAccountChanged); + + HookProtoEvent(PE_VOICE_CALL_STATE, &CDiscordProto::OnVoiceState); + + // database + db_set_resident(m_szModuleName, "XStatusMsg"); + + // custom events + DBEVENTTYPEDESCR dbEventType = {}; + dbEventType.module = m_szModuleName; + dbEventType.flags = DETF_HISTORY | DETF_MSGWINDOW; + + dbEventType.eventType = EVENT_INCOMING_CALL; + dbEventType.descr = Translate("Incoming call"); + dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_CALL); + DbEvent_RegisterType(&dbEventType); + + dbEventType.eventType = EVENT_CALL_FINISHED; + dbEventType.descr = Translate("Call ended"); + dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_ENDED); + DbEvent_RegisterType(&dbEventType); + + // Groupchat initialization + GCREGISTER gcr = {}; + gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR; + gcr.ptszDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + Chat_Register(&gcr); + + // Network initialization + CMStringW descr; + NETLIBUSER nlu = {}; + + nlu.szSettingsModule = m_szModuleName; + nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + descr.Format(TranslateT("%s server connection"), m_tszUserName); + nlu.szDescriptiveName.w = descr.GetBuffer(); + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + CMStringA module(FORMAT, "%s.Gateway", m_szModuleName); + nlu.szSettingsModule = module.GetBuffer(); + nlu.flags = NUF_OUTGOING | NUF_UNICODE; + descr.Format(TranslateT("%s gateway connection"), m_tszUserName); + nlu.szDescriptiveName.w = descr.GetBuffer(); + m_hGatewayNetlibUser = Netlib_RegisterUser(&nlu); +} + +CDiscordProto::~CDiscordProto() +{ + debugLogA("CDiscordProto::~CDiscordProto"); + + for (auto &msg : m_wszStatusMsg) + mir_free(msg); + + arUsers.destroy(); + + m_arHttpQueue.destroy(); + ::CloseHandle(m_evRequestsQueue); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnModulesLoaded() +{ + std::vector lostIds; + + // Fill users list + for (auto &hContact : AccContacts()) { + CDiscordUser *pNew = new CDiscordUser(getId(hContact, DB_KEY_ID)); + pNew->hContact = hContact; + pNew->lastMsgId = getId(hContact, DB_KEY_LASTMSGID); + pNew->wszUsername = ptrW(getWStringA(hContact, DB_KEY_NICK)); + pNew->iDiscriminator = getDword(hContact, DB_KEY_DISCR); + + // set EnableSync = 1 by default for all existing guilds + switch (getByte(hContact, "ChatRoom")) { + case 2: // guild + delSetting(hContact, DB_KEY_CHANNELID); + if (getDword(hContact, "EnableSync", -1) == -1) + setDword(hContact, "EnableSync", 1); + break; + + case 1: // group chat + pNew->channelId = getId(hContact, DB_KEY_CHANNELID); + if (!pNew->channelId) { + lostIds.push_back(hContact); + delete pNew; + continue; + } + break; + + default: + pNew->channelId = getId(hContact, DB_KEY_CHANNELID); + break; + } + arUsers.insert(pNew); + } + + for (auto &hContact: lostIds) + db_delete_contact(hContact); + + // Clist + Clist_GroupCreate(0, m_wszDefaultGroup); + + HookProtoEvent(ME_GC_EVENT, &CDiscordProto::GroupchatEventHook); + HookProtoEvent(ME_GC_BUILDMENU, &CDiscordProto::GroupchatMenuHook); + + InitMenus(); + + // Voice support + if (g_plugin.bVoiceService) { + VOICE_MODULE voice = {}; + voice.cbSize = sizeof(voice); + voice.name = m_szModuleName; + voice.description = TranslateT("Discord voice call"); + voice.icon = m_hProtoIcon; + voice.flags = VOICE_CAPS_CALL_CONTACT | VOICE_CAPS_VOICE; + CallService(MS_VOICESERVICE_REGISTER, (WPARAM)&voice, 0); + } +} + +void CDiscordProto::OnShutdown() +{ + debugLogA("CDiscordProto::OnPreShutdown"); + + m_bTerminated = true; + SetEvent(m_evRequestsQueue); + + for (auto &it : arGuilds) + it->SaveToFile(); + + if (m_hGatewayConnection) + Netlib_Shutdown(m_hGatewayConnection); + + if (g_plugin.bVoiceService) + CallService(MS_VOICESERVICE_UNREGISTER, (WPARAM)m_szModuleName, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_MODEMSG | PF1_MODEMSGRECV | PF1_SERVERCLIST | PF1_BASICSEARCH | PF1_EXTSEARCH | PF1_ADDSEARCHRES | PF1_FILESEND; + case PFLAGNUM_2: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_INVISIBLE; + case PFLAGNUM_3: + return PF2_ONLINE | PF2_LONGAWAY | PF2_HEAVYDND | PF2_INVISIBLE; + case PFLAGNUM_4: + return PF4_FORCEAUTH | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_SUPPORTIDLE | PF4_AVATARS | PF4_IMSENDOFFLINE | PF4_SERVERMSGID | PF4_OFFLINEFILES; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)TranslateT("User ID"); + } + return 0; +} + +int CDiscordProto::SetStatus(int iNewStatus) +{ + debugLogA("CDiscordProto::SetStatus iNewStatus = %d, m_iStatus = %d, m_iDesiredStatus = %d m_hWorkerThread = %p", iNewStatus, m_iStatus, m_iDesiredStatus, m_hWorkerThread); + + if (iNewStatus == m_iStatus) + return 0; + + m_iDesiredStatus = iNewStatus; + int iOldStatus = m_iStatus; + + // go offline + if (iNewStatus == ID_STATUS_OFFLINE) { + if (m_bOnline) { + SetServerStatus(ID_STATUS_OFFLINE); + ShutdownSession(); + } + m_iStatus = m_iDesiredStatus; + setAllContactStatuses(ID_STATUS_OFFLINE, false); + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + } + // not logged in? come on + else if (m_hWorkerThread == nullptr && !IsStatusConnecting(m_iStatus)) { + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + m_hWorkerThread = ForkThreadEx(&CDiscordProto::ServerThread, nullptr, nullptr); + } + else if (m_bOnline) { + debugLogA("setting server online status to %d", iNewStatus); + SetServerStatus(iNewStatus); + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetFocus(GetDlgItem(hwndDlg, IDC_NICK)); + return TRUE; + + case WM_COMMAND: + if (HIWORD(wParam) == EN_SETFOCUS) + PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg); + } + return FALSE; +} + +HWND CDiscordProto::CreateExtendedSearchUI(HWND hwndParent) +{ + if (hwndParent) + return CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_EXTSEARCH), hwndParent, AdvancedSearchDlgProc, 0); + + return nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::SearchThread(void *param) +{ + Sleep(100); + + PROTOSEARCHRESULT psr = { 0 }; + psr.cbSize = sizeof(psr); + psr.flags = PSR_UNICODE; + psr.nick.w = (wchar_t*)param; + psr.firstName.w = L""; + psr.lastName.w = L""; + psr.id.w = L""; + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)1, (LPARAM)&psr); + + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0); + mir_free(param); +} + +HWND CDiscordProto::SearchAdvanced(HWND hwndDlg) +{ + if (!m_bOnline || !IsWindow(hwndDlg)) + return nullptr; + + wchar_t wszNick[200]; + GetDlgItemTextW(hwndDlg, IDC_NICK, wszNick, _countof(wszNick)); + if (wszNick[0] == 0) // empty string? reject + return nullptr; + + wchar_t *p = wcschr(wszNick, '#'); + if (p == nullptr) // wrong user id + return nullptr; + + ForkThread(&CDiscordProto::SearchThread, mir_wstrdup(wszNick)); + return (HWND)1; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Basic search - by SnowFlake + +void CDiscordProto::OnReceiveUserinfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + JsonReply root(pReply); + if (!root) { + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)1); + return; + } + + auto &data = root.data(); + CMStringW wszUserId(data["username"].as_mstring() + L"#" + data["discriminator"].as_mstring()); + ForkThread(&CDiscordProto::SearchThread, wszUserId.Detach()); +} + +HANDLE CDiscordProto::SearchBasic(const wchar_t *wszId) +{ + if (!m_bOnline) + return nullptr; + + CMStringA szUrl = "/users/"; + szUrl.AppendFormat(ptrA(mir_utf8encodeW(wszId))); + Push(new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveUserinfo)); + return (HANDLE)1; // Success +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Authorization + +int CDiscordProto::AuthRequest(MCONTACT hContact, const wchar_t*) +{ + ptrW wszUsername(getWStringA(hContact, DB_KEY_NICK)); + int iDiscriminator(getDword(hContact, DB_KEY_DISCR, -1)); + if (wszUsername == nullptr || iDiscriminator == -1) + return 1; // error + + JSONNode root; root << WCHAR_PARAM("username", wszUsername) << INT_PARAM("discriminator", iDiscriminator); + Push(new AsyncHttpRequest(this, REQUEST_POST, "/users/@me/relationships", nullptr, &root)); + return 0; +} + +int CDiscordProto::AuthRecv(MCONTACT, PROTORECVEVENT *pre) +{ + return Proto_AuthRecv(m_szModuleName, pre); +} + +int CDiscordProto::Authorize(MEVENT hDbEvent) +{ + DB::EventInfo dbei; + dbei.cbBlob = -1; + if (db_event_get(hDbEvent, &dbei)) return 1; + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return 1; + if (mir_strcmp(dbei.szModule, m_szModuleName)) return 1; + + JSONNode root; + MCONTACT hContact = DbGetAuthEventContact(&dbei); + CMStringA szUrl(FORMAT, "/users/@me/relationships/%lld", getId(hContact, DB_KEY_ID)); + Push(new AsyncHttpRequest(this, REQUEST_PUT, szUrl, nullptr, &root)); + return 0; +} + +int CDiscordProto::AuthDeny(MEVENT hDbEvent, const wchar_t*) +{ + DB::EventInfo dbei; + dbei.cbBlob = -1; + if (db_event_get(hDbEvent, &dbei)) return 1; + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return 1; + if (mir_strcmp(dbei.szModule, m_szModuleName)) return 1; + + MCONTACT hContact = DbGetAuthEventContact(&dbei); + RemoveFriend(getId(hContact, DB_KEY_ID)); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// + +MCONTACT CDiscordProto::AddToList(int flags, PROTOSEARCHRESULT *psr) +{ + if (mir_wstrlen(psr->nick.w) == 0) + return 0; + + wchar_t *p = wcschr(psr->nick.w, '#'); + if (p == nullptr) + return 0; + + MCONTACT hContact = db_add_contact(); + Proto_AddToContact(hContact, m_szModuleName); + if (flags & PALF_TEMPORARY) + Contact::RemoveFromList(hContact); + + *p = 0; + CDiscordUser *pUser = new CDiscordUser(0); + pUser->hContact = hContact; + pUser->wszUsername = psr->nick.w; + pUser->iDiscriminator = _wtoi(p + 1); + *p = '#'; + + if (mir_wstrlen(psr->id.w)) { + pUser->id = _wtoi64(psr->id.w); + setId(hContact, DB_KEY_ID, pUser->id); + } + + Clist_SetGroup(hContact, m_wszDefaultGroup); + setWString(hContact, DB_KEY_NICK, pUser->wszUsername); + setDword(hContact, DB_KEY_DISCR, pUser->iDiscriminator); + arUsers.insert(pUser); + + return hContact; +} + +MCONTACT CDiscordProto::AddToListByEvent(int flags, int, MEVENT hDbEvent) +{ + DB::EventInfo dbei; + dbei.cbBlob = -1; + if (db_event_get(hDbEvent, &dbei)) + return 0; + if (mir_strcmp(dbei.szModule, m_szModuleName)) + return 0; + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) + return 0; + + DB::AUTH_BLOB blob(dbei.pBlob); + if (flags & PALF_TEMPORARY) + Contact::RemoveFromList(blob.get_contact()); + else + Contact::PutOnList(blob.get_contact()); + return blob.get_contact(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// SendMsg + +void CDiscordProto::OnSendMsg(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + JsonReply root(pReply); + if (!root) { + int iReqNum = -1; + for (auto &it : arOwnMessages) + if (it->reqId == pReq->m_iReqNum) { + iReqNum = it->reqId; + arOwnMessages.removeItem(&it); + break; + } + + if (iReqNum != -1) { + CMStringW wszErrorMsg(root.data()["message"].as_mstring()); + if (wszErrorMsg.IsEmpty()) + wszErrorMsg = TranslateT("Message send failed"); + ProtoBroadcastAck(pReq->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)iReqNum, (LPARAM)wszErrorMsg.c_str()); + } + } +} + +int CDiscordProto::SendMsg(MCONTACT hContact, int /*flags*/, const char *pszSrc) +{ + if (!m_bOnline) { + ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)TranslateT("Protocol is offline or user isn't authorized yet")); + return 1; + } + + ptrW wszText(mir_utf8decodeW(pszSrc)); + if (wszText == nullptr) + return 0; + + CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser == nullptr || pUser->id == 0) + return 0; + + // no channel - we need to create one + if (pUser->channelId == 0) { + JSONNode list(JSON_ARRAY); list.set_name("recipients"); list << SINT64_PARAM("", pUser->id); + JSONNode body; body << list; + CMStringA szUrl(FORMAT, "/users/%lld/channels", m_ownId); + + // theoretically we get the same data from the gateway thread, but there could be a delay + // so we bind data analysis to the http packet reply + mir_cslock lck(m_csHttpQueue); + ExecuteRequest(new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveCreateChannel, &body)); + if (pUser->channelId == 0) + return 0; + } + + // we generate a random 64-bit integer and pass it to the server + // to distinguish our own messages from these generated by another clients + SnowFlake nonce; Utils_GetRandom(&nonce, sizeof(nonce)); nonce = abs(nonce); + JSONNode body; body << WCHAR_PARAM("content", wszText) << SINT64_PARAM("nonce", nonce); + + CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnSendMsg, &body); + pReq->hContact = hContact; + arOwnMessages.insert(new COwnMessage(nonce, pReq->m_iReqNum)); + Push(pReq); + return pReq->m_iReqNum; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void __cdecl CDiscordProto::GetAwayMsgThread(void *param) +{ + Thread_SetName("Jabber: GetAwayMsgThread"); + + auto *pUser = (CDiscordUser *)param; + if (pUser == nullptr) + return; + + if (pUser->wszTopic.IsEmpty()) + ProtoBroadcastAck(pUser->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, 0); + else + ProtoBroadcastAck(pUser->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)pUser->wszTopic.c_str()); +} + +HANDLE CDiscordProto::GetAwayMsg(MCONTACT hContact) +{ + ForkThread(&CDiscordProto::GetAwayMsgThread, FindUser(getId(hContact, DB_KEY_ID))); + return (HANDLE)1; +} + +int CDiscordProto::SetAwayMsg(int iStatus, const wchar_t *msg) +{ + if (iStatus < ID_STATUS_MIN || iStatus > ID_STATUS_MAX) + return 0; + + wchar_t *&pwszMessage = m_wszStatusMsg[iStatus - ID_STATUS_MIN]; + if (!mir_wstrcmp(msg, pwszMessage)) + return 0; + + replaceStrW(pwszMessage, msg); + + if (m_bOnline) { + JSONNode status; status.set_name("custom_status"); status << WCHAR_PARAM("text", (msg) ? msg : L""); + JSONNode root; root << status; + Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me/settings", nullptr, &root)); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CDiscordProto::UserIsTyping(MCONTACT hContact, int type) +{ + if (type == PROTOTYPE_SELFTYPING_ON) { + CMStringA szUrl(FORMAT, "/channels/%lld/typing", getId(hContact, DB_KEY_CHANNELID)); + Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, nullptr)); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnReceiveMarkRead(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) +{ + JsonReply root(pReply); + if (root) + SaveToken(root.data()); +} + +void CDiscordProto::SendMarkRead() +{ + mir_cslock lck(csMarkReadQueue); + while (arMarkReadQueue.getCount()) { + CDiscordUser *pUser = arMarkReadQueue[0]; + JSONNode payload; payload << CHAR_PARAM("token", m_szTempToken); + CMStringA szUrl(FORMAT, "/channels/%lld/messages/%lld/ack", pUser->channelId, pUser->lastMsgId); + auto *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveMarkRead, &payload); + Push(pReq); + arMarkReadQueue.remove(0); + } +} + +int CDiscordProto::OnDbEventRead(WPARAM, LPARAM hDbEvent) +{ + MCONTACT hContact = db_event_getContact(hDbEvent); + if (!hContact) + return 0; + + // filter out only events of my protocol + const char *szProto = Proto_GetBaseAccountName(hContact); + if (mir_strcmp(szProto, m_szModuleName)) + return 0; + + if (m_bOnline) { + m_impl.m_markRead.Start(200); + + CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser != nullptr) { + mir_cslock lck(csMarkReadQueue); + if (arMarkReadQueue.indexOf(pUser) == -1) + arMarkReadQueue.insert(pUser); + } + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CDiscordProto::OnAccountChanged(WPARAM iAction, LPARAM lParam) +{ + if (iAction == PRAC_ADDED) { + PROTOACCOUNT *pa = (PROTOACCOUNT*)lParam; + if (pa && pa->ppro == this) { + m_bUseGroupchats = false; + m_bUseGuildGroups = true; + } + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnContactDeleted(MCONTACT hContact) +{ + CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser == nullptr || !m_bOnline) + return; + + if (pUser->channelId) + Push(new AsyncHttpRequest(this, REQUEST_DELETE, CMStringA(FORMAT, "/channels/%lld", pUser->channelId), nullptr)); + + if (pUser->id) + RemoveFriend(pUser->id); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::RequestFriendship(WPARAM hContact, LPARAM) +{ + AuthRequest(hContact, 0); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +struct SendFileThreadParam +{ + MCONTACT hContact; + CMStringW wszDescr, wszFileName; + + SendFileThreadParam(MCONTACT _p1, LPCWSTR _p2, LPCWSTR _p3) : + hContact(_p1), + wszFileName(_p2), + wszDescr(_p3) + {} +}; + +void CDiscordProto::SendFileThread(void *param) +{ + SendFileThreadParam *p = (SendFileThreadParam*)param; + + FILE *in = _wfopen(p->wszFileName, L"rb"); + if (in == nullptr) { + debugLogA("cannot open file %S for reading", p->wszFileName.c_str()); + LBL_Error: + ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, param); + delete p; + return; + } + + ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, param); + + char szRandom[16], szRandomText[33]; + Utils_GetRandom(szRandom, _countof(szRandom)); + bin2hex(szRandom, _countof(szRandom), szRandomText); + CMStringA szBoundary(FORMAT, "----Boundary%s", szRandomText); + + CMStringA szUrl(FORMAT, "/channels/%lld/messages", getId(p->hContact, DB_KEY_CHANNELID)); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveFile); + pReq->AddHeader("Content-Type", CMStringA("multipart/form-data; boundary=" + szBoundary)); + pReq->AddHeader("Accept", "*/*"); + + szBoundary.Insert(0, "--"); + + CMStringA szBody; + szBody.Append(szBoundary + "\r\n"); + szBody.Append("Content-Disposition: form-data; name=\"content\"\r\n\r\n"); + szBody.Append(ptrA(mir_utf8encodeW(p->wszDescr))); + szBody.Append("\r\n"); + + szBody.Append(szBoundary + "\r\n"); + szBody.Append("Content-Disposition: form-data; name=\"tts\"\r\n\r\nfalse\r\n"); + + wchar_t *pFileName = wcsrchr(p->wszFileName.GetBuffer(), '\\'); + if (pFileName != nullptr) + pFileName++; + else + pFileName = p->wszFileName.GetBuffer(); + + szBody.Append(szBoundary + "\r\n"); + szBody.AppendFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", ptrA(mir_utf8encodeW(pFileName)).get()); + szBody.AppendFormat("Content-Type: %S\r\n", ProtoGetAvatarMimeType(ProtoGetAvatarFileFormat(p->wszFileName))); + szBody.Append("\r\n"); + + size_t cbBytes = filelength(fileno(in)); + + szBoundary.Insert(0, "\r\n"); + szBoundary.Append("--\r\n"); + pReq->dataLength = int(szBody.GetLength() + szBoundary.GetLength() + cbBytes); + pReq->pData = (char*)mir_alloc(pReq->dataLength+1); + memcpy(pReq->pData, szBody.c_str(), szBody.GetLength()); + size_t cbRead = fread(pReq->pData + szBody.GetLength(), 1, cbBytes, in); + fclose(in); + if (cbBytes != cbRead) { + debugLogA("cannot read file %S: %d bytes read instead of %d", p->wszFileName.c_str(), cbRead, cbBytes); + delete pReq; + goto LBL_Error; + } + + memcpy(pReq->pData + szBody.GetLength() + cbBytes, szBoundary, szBoundary.GetLength()); + pReq->pUserInfo = p; + Push(pReq); + + ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, param); +} + +void CDiscordProto::OnReceiveFile(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + SendFileThreadParam *p = (SendFileThreadParam*)pReq->pUserInfo; + if (pReply->resultCode != 200) { + ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, p); + debugLogA("CDiscordProto::SendFile failed: %d", pReply->resultCode); + } + else { + ProtoBroadcastAck(p->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, p); + debugLogA("CDiscordProto::SendFile succeeded"); + } + + delete p; +} + +HANDLE CDiscordProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) +{ + SnowFlake id = getId(hContact, DB_KEY_CHANNELID); + if (id == 0) + return nullptr; + + // we don't wanna block the main thread, right? + SendFileThreadParam *param = new SendFileThreadParam(hContact, ppszFiles[0], szDescription); + ForkThread(&CDiscordProto::SendFileThread, param); + return param; +} diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index bf3929fd55..b5262f4e0a 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -1,476 +1,476 @@ -#pragma once - -#define EVENT_INCOMING_CALL 10001 -#define EVENT_CALL_FINISHED 10002 - -typedef __int64 SnowFlake; - -__forceinline int compareInt64(const SnowFlake i1, const SnowFlake i2) -{ - return (i1 == i2) ? 0 : (i1 < i2) ? -1 : 1; -} - -class CDiscordProto; -typedef void (CDiscordProto::*GatewayHandlerFunc)(const JSONNode&); - -struct AsyncHttpRequest : public MTHttpRequest -{ - AsyncHttpRequest(CDiscordProto*, int iRequestType, LPCSTR szUrl, MTHttpRequestHandler pFunc, JSONNode *pNode = nullptr); - - int m_iErrorCode, m_iReqNum; - bool m_bMainSite; - MCONTACT hContact; -}; - -class JsonReply -{ - JSONNode *m_root = nullptr; - int m_errorCode = 0; - -public: - JsonReply(NETLIBHTTPREQUEST *); - ~JsonReply(); - - __forceinline int error() const { return m_errorCode; } - __forceinline JSONNode& data() const { return *m_root; } - __forceinline operator bool() const { return m_errorCode == 200; } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CDiscordRole : public MZeroedObject -{ - SnowFlake id; - COLORREF color; - uint32_t permissions; - int position; - CMStringW wszName; -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct COwnMessage -{ - SnowFlake nonce; - int reqId; - - COwnMessage(SnowFlake _id, int _reqId) : - nonce(_id), - reqId(_reqId) - {} -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -enum CDiscordHistoryOp -{ - MSG_NOFILTER, MSG_AFTER, MSG_BEFORE -}; - -struct CDiscordUser : public MZeroedObject -{ - CDiscordUser(SnowFlake _id) : - id(_id) - {} - - ~CDiscordUser(); - - SnowFlake id; - MCONTACT hContact; - - SnowFlake channelId; - SnowFlake lastReadId, lastMsgId; - SnowFlake parentId; - bool bIsPrivate; - bool bIsGroup; - bool bSynced; - - struct CDiscordGuild *pGuild; - - CMStringW wszUsername, wszChannelName, wszTopic; - int iDiscriminator; -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CDiscordGuildMember : public MZeroedObject -{ - CDiscordGuildMember(SnowFlake id) : - userId(id) - {} - - ~CDiscordGuildMember() - {} - - SnowFlake userId; - CMStringW wszDiscordId, wszNick, wszRole; - int iStatus; -}; - -struct CDiscordGuild : public MZeroedObject -{ - CDiscordGuild(SnowFlake _id); - ~CDiscordGuild(); - - __forceinline CDiscordGuildMember* FindUser(SnowFlake userId) - { - return arChatUsers.find((CDiscordGuildMember *)&userId); - } - - __inline CMStringW GetCacheFile() const - { - return CMStringW(FORMAT, L"%s\\DiscordCache\\%lld.json", VARSW(L"%miranda_userdata%").get(), id); - } - - SnowFlake id, ownerId; - CMStringW wszName; - MCONTACT hContact; - MGROUP groupId; - bool bSynced = false; - LIST arChannels; - - SESSION_INFO *pParentSi; - OBJLIST arChatUsers; - OBJLIST arRoles; // guild roles - - void LoadFromFile(); - void SaveToFile(); -}; - -struct CDiscordVoiceCall -{ - CMStringA szId; - SnowFlake channelId; - time_t startTime; -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -#define OPCODE_DISPATCH 0 -#define OPCODE_HEARTBEAT 1 -#define OPCODE_IDENTIFY 2 -#define OPCODE_STATUS_UPDATE 3 -#define OPCODE_VOICE_UPDATE 4 -#define OPCODE_VOICE_PING 5 -#define OPCODE_RESUME 6 -#define OPCODE_RECONNECT 7 -#define OPCODE_REQUEST_MEMBERS 8 -#define OPCODE_INVALID_SESSION 9 -#define OPCODE_HELLO 10 -#define OPCODE_HEARTBEAT_ACK 11 -#define OPCODE_REQUEST_SYNC 12 -#define OPCODE_REQUEST_SYNC_GROUP 13 -#define OPCODE_REQUEST_SYNC_CHANNEL 14 - -class CDiscordProto : public PROTO -{ - friend struct AsyncHttpRequest; - friend class CDiscardAccountOptions; - - class CDiscordProtoImpl - { - friend class CDiscordProto; - CDiscordProto &m_proto; - - CTimer m_heartBeat, m_markRead; - void OnHeartBeat(CTimer *) { - m_proto.GatewaySendHeartbeat(); - } - - void OnMarkRead(CTimer *pTimer) { - m_proto.SendMarkRead(); - pTimer->Stop(); - } - - CDiscordProtoImpl(CDiscordProto &pro) : - m_proto(pro), - m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), - m_heartBeat(Miranda_GetSystemWindow(), UINT_PTR(this) + 1) - { - m_markRead.OnEvent = Callback(this, &CDiscordProtoImpl::OnMarkRead); - m_heartBeat.OnEvent = Callback(this, &CDiscordProtoImpl::OnHeartBeat); - } - } m_impl; - - ////////////////////////////////////////////////////////////////////////////////////// - // threads - - void __cdecl SendFileThread(void*); - void __cdecl ServerThread(void*); - void __cdecl SearchThread(void *param); - void __cdecl BatchChatCreate(void* param); - void __cdecl GetAwayMsgThread(void *param); - - ////////////////////////////////////////////////////////////////////////////////////// - // session control - - void ConnectionFailed(int iReason); - void ShutdownSession(void); - - wchar_t *m_wszStatusMsg[MAX_STATUS_COUNT]; - - ptrA m_szAccessToken, m_szTempToken; - - mir_cs m_csHttpQueue; - HANDLE m_evRequestsQueue; - LIST m_arHttpQueue; - - void ExecuteRequest(AsyncHttpRequest *pReq); - void Push(AsyncHttpRequest *pReq, int iTimeout = 10000); - void SaveToken(const JSONNode &data); - - HANDLE m_hWorkerThread; // worker thread handle - HNETLIBCONN m_hAPIConnection; // working connection - - bool - m_bOnline, // protocol is online - m_bTerminated; // Miranda's going down - - ////////////////////////////////////////////////////////////////////////////////////// - // gateway - - CMStringA - m_szGateway, // gateway url - m_szGatewaySessionId, // current session id - m_szCookie, // cookie used for all http queries - m_szWSCookie; // cookie used for establishing websocket connection - - HNETLIBUSER m_hGatewayNetlibUser; // the separate netlib user handle for gateways - HNETLIBCONN m_hGatewayConnection; // gateway connection - - void __cdecl GatewayThread(void*); - bool GatewayThreadWorker(void); - - bool GatewaySend(const JSONNode &pNode); - bool GatewayProcess(const JSONNode &pNode); - - void GatewaySendGuildInfo(CDiscordGuild *pGuild); - void GatewaySendHeartbeat(void); - void GatewaySendIdentify(void); - void GatewaySendResume(void); - bool GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText); - - GatewayHandlerFunc GetHandler(const wchar_t*); - - int m_iHartbeatInterval; // in milliseconds - int m_iGatewaySeq; // gateway sequence number - - ////////////////////////////////////////////////////////////////////////////////////// - // options - - CMOption m_wszEmail; // my own email - CMOption m_wszDefaultGroup; // clist group to store contacts - CMOption m_bUseGroupchats; // Shall we connect Guilds at all? - CMOption m_bHideGroupchats; // Do not open chat windows on creation - CMOption m_bUseGuildGroups; // use special subgroups for guilds - CMOption m_bSyncDeleteMsgs; // delete messages from Miranda if they are deleted at the server - - ////////////////////////////////////////////////////////////////////////////////////// - // common data - - SnowFlake m_ownId; - - mir_cs csMarkReadQueue; - LIST arMarkReadQueue; - - OBJLIST arUsers; - OBJLIST arOwnMessages; - OBJLIST arVoiceCalls; - - CDiscordUser* FindUser(SnowFlake id); - CDiscordUser* FindUser(const wchar_t *pwszUsername, int iDiscriminator); - CDiscordUser* FindUserByChannel(SnowFlake channelId); - - void PreparePrivateChannel(const JSONNode &); - CDiscordUser* PrepareUser(const JSONNode &); - - ////////////////////////////////////////////////////////////////////////////////////// - // menu items - - void InitMenus(void); - - int __cdecl OnMenuPrebuild(WPARAM, LPARAM); - - INT_PTR __cdecl OnMenuCopyId(WPARAM, LPARAM); - INT_PTR __cdecl OnMenuCreateChannel(WPARAM, LPARAM); - INT_PTR __cdecl OnMenuJoinGuild(WPARAM, LPARAM); - INT_PTR __cdecl OnMenuLeaveGuild(WPARAM, LPARAM); - INT_PTR __cdecl OnMenuLoadHistory(WPARAM, LPARAM); - INT_PTR __cdecl OnMenuToggleSync(WPARAM, LPARAM); - - HGENMENU m_hMenuLeaveGuild, m_hMenuCreateChannel, m_hMenuToggleSync; - - ////////////////////////////////////////////////////////////////////////////////////// - // guilds - - OBJLIST arGuilds; - - __forceinline CDiscordGuild* FindGuild(SnowFlake id) const - { - return arGuilds.find((CDiscordGuild*)&id); - } - - void AddGuildUser(CDiscordGuild *guild, const CDiscordGuildMember &pUser); - void ProcessGuild(const JSONNode &json); - void ProcessPresence(const JSONNode &json); - void ProcessRole(CDiscordGuild *guild, const JSONNode &json); - void ProcessType(CDiscordUser *pUser, const JSONNode &json); - - CDiscordUser* ProcessGuildChannel(CDiscordGuild *guild, const JSONNode &json); - CDiscordGuildMember* ProcessGuildUser(CDiscordGuild *pGuild, const JSONNode &json, bool *bNew = nullptr); - - ////////////////////////////////////////////////////////////////////////////////////// - // group chats - - int __cdecl GroupchatEventHook(WPARAM, LPARAM); - int __cdecl GroupchatMenuHook(WPARAM, LPARAM); - - void Chat_SendPrivateMessage(GCHOOK *gch); - void Chat_ProcessLogMenu(GCHOOK *gch); - void Chat_ProcessNickMenu(GCHOOK* gch); - - void CreateChat(CDiscordGuild *pGuild, CDiscordUser *pUser); - void ProcessChatUser(CDiscordUser *pChat, const CMStringW &wszUserId, const JSONNode &pRoot); - void ParseSpecialChars(SESSION_INFO *si, CMStringW &str); - - ////////////////////////////////////////////////////////////////////////////////////// - // misc methods - - SnowFlake getId(const char *szName); - SnowFlake getId(MCONTACT hContact, const char *szName); - - void setId(const char *szName, SnowFlake iValue); - void setId(MCONTACT hContact, const char *szName, SnowFlake iValue); - -public: - CDiscordProto(const char*,const wchar_t*); - ~CDiscordProto(); - - ////////////////////////////////////////////////////////////////////////////////////// - // PROTO_INTERFACE - - INT_PTR GetCaps(int, MCONTACT = 0) override; - - HWND CreateExtendedSearchUI(HWND owner) override; - HWND SearchAdvanced(HWND owner) override; - - HANDLE SearchBasic(const wchar_t *id) override; - MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; - MCONTACT AddToListByEvent(int flags, int, MEVENT hDbEvent) override; - - int AuthRecv(MCONTACT, PROTORECVEVENT *pre) override; - int Authorize(MEVENT hDbEvent) override; - int AuthDeny(MEVENT hDbEvent, const wchar_t* szReason) override; - int AuthRequest(MCONTACT hContact, const wchar_t*) override; - - HANDLE GetAwayMsg(MCONTACT hContact) override; - int SetAwayMsg(int iStatus, const wchar_t *msg) override; - - int SendMsg(MCONTACT hContact, int flags, const char *pszSrc) override; - - HANDLE SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) override; - - int UserIsTyping(MCONTACT hContact, int type) override; - - int SetStatus(int iNewStatus) override; - - void OnBuildProtoMenu() override; - void OnContactDeleted(MCONTACT) override; - void OnModulesLoaded() override; - void OnShutdown() override; - - ////////////////////////////////////////////////////////////////////////////////////// - // Services - - INT_PTR __cdecl RequestFriendship(WPARAM, LPARAM); - INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - - INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); - INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); - INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM); - INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM); - - INT_PTR __cdecl VoiceCaps(WPARAM, LPARAM); - - ////////////////////////////////////////////////////////////////////////////////////// - // Events - - int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnAccountChanged(WPARAM, LPARAM); - int __cdecl OnDbEventRead(WPARAM, LPARAM); - - int __cdecl OnVoiceState(WPARAM, LPARAM); - - ////////////////////////////////////////////////////////////////////////////////////// - // dispatch commands - - void OnCommandCallCreated(const JSONNode &json); - void OnCommandCallDeleted(const JSONNode &json); - void OnCommandCallUpdated(const JSONNode &json); - void OnCommandChannelCreated(const JSONNode &json); - void OnCommandChannelDeleted(const JSONNode &json); - void OnCommandChannelUpdated(const JSONNode &json); - void OnCommandGuildCreated(const JSONNode &json); - void OnCommandGuildDeleted(const JSONNode &json); - void OnCommandGuildMemberAdded(const JSONNode &json); - void OnCommandGuildMemberListUpdate(const JSONNode &json); - void OnCommandGuildMemberRemoved(const JSONNode &json); - void OnCommandGuildMemberUpdated(const JSONNode &json); - void OnCommandFriendAdded(const JSONNode &json); - void OnCommandFriendRemoved(const JSONNode &json); - void OnCommandMessage(const JSONNode&, bool); - void OnCommandMessageCreate(const JSONNode &json); - void OnCommandMessageDelete(const JSONNode &json); - void OnCommandMessageUpdate(const JSONNode &json); - void OnCommandMessageAck(const JSONNode &json); - void OnCommandPresence(const JSONNode &json); - void OnCommandReady(const JSONNode &json); - void OnCommandRoleCreated(const JSONNode &json); - void OnCommandRoleDeleted(const JSONNode &json); - void OnCommandTyping(const JSONNode &json); - void OnCommandUserUpdate(const JSONNode &json); - void OnCommandUserSettingsUpdate(const JSONNode &json); - - void OnLoggedIn(); - void OnLoggedOut(); - - void OnReceiveCreateChannel(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveFile(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveMarkRead(NETLIBHTTPREQUEST *, AsyncHttpRequest *); - void OnReceiveMessageAck(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveToken(NETLIBHTTPREQUEST *, AsyncHttpRequest *); - void OnReceiveUserinfo(NETLIBHTTPREQUEST *, AsyncHttpRequest *); - - void RetrieveMyInfo(); - void OnReceiveMyInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - - void RetrieveHistory(CDiscordUser *pUser, CDiscordHistoryOp iOp = MSG_NOFILTER, SnowFlake msgid = 0, int iLimit = 50); - void OnReceiveHistory(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - - bool RetrieveAvatar(MCONTACT hContact); - void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - - void OnSendMsg(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - - ////////////////////////////////////////////////////////////////////////////////////// - // Misc - - void SendMarkRead(void); - void SetServerStatus(int iStatus); - void RemoveFriend(SnowFlake id); - - CMStringW GetAvatarFilename(MCONTACT hContact); - void CheckAvatarChange(MCONTACT hContact, const CMStringW &wszNewHash); -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CMPlugin : public ACCPROTOPLUGIN -{ - CMPlugin(); - - bool bVoiceService = false; - - int Load() override; -}; +#pragma once + +#define EVENT_INCOMING_CALL 10001 +#define EVENT_CALL_FINISHED 10002 + +typedef __int64 SnowFlake; + +__forceinline int compareInt64(const SnowFlake i1, const SnowFlake i2) +{ + return (i1 == i2) ? 0 : (i1 < i2) ? -1 : 1; +} + +class CDiscordProto; +typedef void (CDiscordProto::*GatewayHandlerFunc)(const JSONNode&); + +struct AsyncHttpRequest : public MTHttpRequest +{ + AsyncHttpRequest(CDiscordProto*, int iRequestType, LPCSTR szUrl, MTHttpRequestHandler pFunc, JSONNode *pNode = nullptr); + + int m_iErrorCode, m_iReqNum; + bool m_bMainSite; + MCONTACT hContact; +}; + +class JsonReply +{ + JSONNode *m_root = nullptr; + int m_errorCode = 0; + +public: + JsonReply(NETLIBHTTPREQUEST *); + ~JsonReply(); + + __forceinline int error() const { return m_errorCode; } + __forceinline JSONNode& data() const { return *m_root; } + __forceinline operator bool() const { return m_errorCode == 200; } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct CDiscordRole : public MZeroedObject +{ + SnowFlake id; + COLORREF color; + uint32_t permissions; + int position; + CMStringW wszName; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct COwnMessage +{ + SnowFlake nonce; + int reqId; + + COwnMessage(SnowFlake _id, int _reqId) : + nonce(_id), + reqId(_reqId) + {} +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +enum CDiscordHistoryOp +{ + MSG_NOFILTER, MSG_AFTER, MSG_BEFORE +}; + +struct CDiscordUser : public MZeroedObject +{ + CDiscordUser(SnowFlake _id) : + id(_id) + {} + + ~CDiscordUser(); + + SnowFlake id; + MCONTACT hContact; + + SnowFlake channelId; + SnowFlake lastReadId, lastMsgId; + SnowFlake parentId; + bool bIsPrivate; + bool bIsGroup; + bool bSynced; + + struct CDiscordGuild *pGuild; + + CMStringW wszUsername, wszChannelName, wszTopic; + int iDiscriminator; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct CDiscordGuildMember : public MZeroedObject +{ + CDiscordGuildMember(SnowFlake id) : + userId(id) + {} + + ~CDiscordGuildMember() + {} + + SnowFlake userId; + CMStringW wszDiscordId, wszNick, wszRole; + int iStatus; +}; + +struct CDiscordGuild : public MZeroedObject +{ + CDiscordGuild(SnowFlake _id); + ~CDiscordGuild(); + + __forceinline CDiscordGuildMember* FindUser(SnowFlake userId) + { + return arChatUsers.find((CDiscordGuildMember *)&userId); + } + + __inline CMStringW GetCacheFile() const + { + return CMStringW(FORMAT, L"%s\\DiscordCache\\%lld.json", VARSW(L"%miranda_userdata%").get(), id); + } + + SnowFlake id, ownerId; + CMStringW wszName; + MCONTACT hContact; + MGROUP groupId; + bool bSynced = false; + LIST arChannels; + + SESSION_INFO *pParentSi; + OBJLIST arChatUsers; + OBJLIST arRoles; // guild roles + + void LoadFromFile(); + void SaveToFile(); +}; + +struct CDiscordVoiceCall +{ + CMStringA szId; + SnowFlake channelId; + time_t startTime; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +#define OPCODE_DISPATCH 0 +#define OPCODE_HEARTBEAT 1 +#define OPCODE_IDENTIFY 2 +#define OPCODE_STATUS_UPDATE 3 +#define OPCODE_VOICE_UPDATE 4 +#define OPCODE_VOICE_PING 5 +#define OPCODE_RESUME 6 +#define OPCODE_RECONNECT 7 +#define OPCODE_REQUEST_MEMBERS 8 +#define OPCODE_INVALID_SESSION 9 +#define OPCODE_HELLO 10 +#define OPCODE_HEARTBEAT_ACK 11 +#define OPCODE_REQUEST_SYNC 12 +#define OPCODE_REQUEST_SYNC_GROUP 13 +#define OPCODE_REQUEST_SYNC_CHANNEL 14 + +class CDiscordProto : public PROTO +{ + friend struct AsyncHttpRequest; + friend class CDiscardAccountOptions; + + class CDiscordProtoImpl + { + friend class CDiscordProto; + CDiscordProto &m_proto; + + CTimer m_heartBeat, m_markRead; + void OnHeartBeat(CTimer *) { + m_proto.GatewaySendHeartbeat(); + } + + void OnMarkRead(CTimer *pTimer) { + m_proto.SendMarkRead(); + pTimer->Stop(); + } + + CDiscordProtoImpl(CDiscordProto &pro) : + m_proto(pro), + m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), + m_heartBeat(Miranda_GetSystemWindow(), UINT_PTR(this) + 1) + { + m_markRead.OnEvent = Callback(this, &CDiscordProtoImpl::OnMarkRead); + m_heartBeat.OnEvent = Callback(this, &CDiscordProtoImpl::OnHeartBeat); + } + } m_impl; + + ////////////////////////////////////////////////////////////////////////////////////// + // threads + + void __cdecl SendFileThread(void*); + void __cdecl ServerThread(void*); + void __cdecl SearchThread(void *param); + void __cdecl BatchChatCreate(void* param); + void __cdecl GetAwayMsgThread(void *param); + + ////////////////////////////////////////////////////////////////////////////////////// + // session control + + void ConnectionFailed(int iReason); + void ShutdownSession(void); + + wchar_t *m_wszStatusMsg[MAX_STATUS_COUNT]; + + ptrA m_szAccessToken, m_szTempToken; + + mir_cs m_csHttpQueue; + HANDLE m_evRequestsQueue; + LIST m_arHttpQueue; + + void ExecuteRequest(AsyncHttpRequest *pReq); + void Push(AsyncHttpRequest *pReq, int iTimeout = 10000); + void SaveToken(const JSONNode &data); + + HANDLE m_hWorkerThread; // worker thread handle + HNETLIBCONN m_hAPIConnection; // working connection + + bool + m_bOnline, // protocol is online + m_bTerminated; // Miranda's going down + + ////////////////////////////////////////////////////////////////////////////////////// + // gateway + + CMStringA + m_szGateway, // gateway url + m_szGatewaySessionId, // current session id + m_szCookie, // cookie used for all http queries + m_szWSCookie; // cookie used for establishing websocket connection + + HNETLIBUSER m_hGatewayNetlibUser; // the separate netlib user handle for gateways + HNETLIBCONN m_hGatewayConnection; // gateway connection + + void __cdecl GatewayThread(void*); + bool GatewayThreadWorker(void); + + bool GatewaySend(const JSONNode &pNode); + bool GatewayProcess(const JSONNode &pNode); + + void GatewaySendGuildInfo(CDiscordGuild *pGuild); + void GatewaySendHeartbeat(void); + void GatewaySendIdentify(void); + void GatewaySendResume(void); + bool GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText); + + GatewayHandlerFunc GetHandler(const wchar_t*); + + int m_iHartbeatInterval; // in milliseconds + int m_iGatewaySeq; // gateway sequence number + + ////////////////////////////////////////////////////////////////////////////////////// + // options + + CMOption m_wszEmail; // my own email + CMOption m_wszDefaultGroup; // clist group to store contacts + CMOption m_bUseGroupchats; // Shall we connect Guilds at all? + CMOption m_bHideGroupchats; // Do not open chat windows on creation + CMOption m_bUseGuildGroups; // use special subgroups for guilds + CMOption m_bSyncDeleteMsgs; // delete messages from Miranda if they are deleted at the server + + ////////////////////////////////////////////////////////////////////////////////////// + // common data + + SnowFlake m_ownId; + + mir_cs csMarkReadQueue; + LIST arMarkReadQueue; + + OBJLIST arUsers; + OBJLIST arOwnMessages; + OBJLIST arVoiceCalls; + + CDiscordUser* FindUser(SnowFlake id); + CDiscordUser* FindUser(const wchar_t *pwszUsername, int iDiscriminator); + CDiscordUser* FindUserByChannel(SnowFlake channelId); + + void PreparePrivateChannel(const JSONNode &); + CDiscordUser* PrepareUser(const JSONNode &); + + ////////////////////////////////////////////////////////////////////////////////////// + // menu items + + void InitMenus(void); + + int __cdecl OnMenuPrebuild(WPARAM, LPARAM); + + INT_PTR __cdecl OnMenuCopyId(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuCreateChannel(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuJoinGuild(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuLeaveGuild(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuLoadHistory(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuToggleSync(WPARAM, LPARAM); + + HGENMENU m_hMenuLeaveGuild, m_hMenuCreateChannel, m_hMenuToggleSync; + + ////////////////////////////////////////////////////////////////////////////////////// + // guilds + + OBJLIST arGuilds; + + __forceinline CDiscordGuild* FindGuild(SnowFlake id) const + { + return arGuilds.find((CDiscordGuild*)&id); + } + + void AddGuildUser(CDiscordGuild *guild, const CDiscordGuildMember &pUser); + void ProcessGuild(const JSONNode &json); + void ProcessPresence(const JSONNode &json); + void ProcessRole(CDiscordGuild *guild, const JSONNode &json); + void ProcessType(CDiscordUser *pUser, const JSONNode &json); + + CDiscordUser* ProcessGuildChannel(CDiscordGuild *guild, const JSONNode &json); + CDiscordGuildMember* ProcessGuildUser(CDiscordGuild *pGuild, const JSONNode &json, bool *bNew = nullptr); + + ////////////////////////////////////////////////////////////////////////////////////// + // group chats + + int __cdecl GroupchatEventHook(WPARAM, LPARAM); + int __cdecl GroupchatMenuHook(WPARAM, LPARAM); + + void Chat_SendPrivateMessage(GCHOOK *gch); + void Chat_ProcessLogMenu(GCHOOK *gch); + void Chat_ProcessNickMenu(GCHOOK* gch); + + void CreateChat(CDiscordGuild *pGuild, CDiscordUser *pUser); + void ProcessChatUser(CDiscordUser *pChat, const CMStringW &wszUserId, const JSONNode &pRoot); + void ParseSpecialChars(SESSION_INFO *si, CMStringW &str); + + ////////////////////////////////////////////////////////////////////////////////////// + // misc methods + + SnowFlake getId(const char *szName); + SnowFlake getId(MCONTACT hContact, const char *szName); + + void setId(const char *szName, SnowFlake iValue); + void setId(MCONTACT hContact, const char *szName, SnowFlake iValue); + +public: + CDiscordProto(const char*,const wchar_t*); + ~CDiscordProto(); + + ////////////////////////////////////////////////////////////////////////////////////// + // PROTO_INTERFACE + + INT_PTR GetCaps(int, MCONTACT = 0) override; + + HWND CreateExtendedSearchUI(HWND owner) override; + HWND SearchAdvanced(HWND owner) override; + + HANDLE SearchBasic(const wchar_t *id) override; + MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; + MCONTACT AddToListByEvent(int flags, int, MEVENT hDbEvent) override; + + int AuthRecv(MCONTACT, PROTORECVEVENT *pre) override; + int Authorize(MEVENT hDbEvent) override; + int AuthDeny(MEVENT hDbEvent, const wchar_t* szReason) override; + int AuthRequest(MCONTACT hContact, const wchar_t*) override; + + HANDLE GetAwayMsg(MCONTACT hContact) override; + int SetAwayMsg(int iStatus, const wchar_t *msg) override; + + int SendMsg(MCONTACT hContact, int flags, const char *pszSrc) override; + + HANDLE SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) override; + + int UserIsTyping(MCONTACT hContact, int type) override; + + int SetStatus(int iNewStatus) override; + + void OnBuildProtoMenu() override; + void OnContactDeleted(MCONTACT) override; + void OnModulesLoaded() override; + void OnShutdown() override; + + ////////////////////////////////////////////////////////////////////////////////////// + // Services + + INT_PTR __cdecl RequestFriendship(WPARAM, LPARAM); + INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); + + INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); + INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); + INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM); + INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM); + + INT_PTR __cdecl VoiceCaps(WPARAM, LPARAM); + + ////////////////////////////////////////////////////////////////////////////////////// + // Events + + int __cdecl OnOptionsInit(WPARAM, LPARAM); + int __cdecl OnAccountChanged(WPARAM, LPARAM); + int __cdecl OnDbEventRead(WPARAM, LPARAM); + + int __cdecl OnVoiceState(WPARAM, LPARAM); + + ////////////////////////////////////////////////////////////////////////////////////// + // dispatch commands + + void OnCommandCallCreated(const JSONNode &json); + void OnCommandCallDeleted(const JSONNode &json); + void OnCommandCallUpdated(const JSONNode &json); + void OnCommandChannelCreated(const JSONNode &json); + void OnCommandChannelDeleted(const JSONNode &json); + void OnCommandChannelUpdated(const JSONNode &json); + void OnCommandGuildCreated(const JSONNode &json); + void OnCommandGuildDeleted(const JSONNode &json); + void OnCommandGuildMemberAdded(const JSONNode &json); + void OnCommandGuildMemberListUpdate(const JSONNode &json); + void OnCommandGuildMemberRemoved(const JSONNode &json); + void OnCommandGuildMemberUpdated(const JSONNode &json); + void OnCommandFriendAdded(const JSONNode &json); + void OnCommandFriendRemoved(const JSONNode &json); + void OnCommandMessage(const JSONNode&, bool); + void OnCommandMessageCreate(const JSONNode &json); + void OnCommandMessageDelete(const JSONNode &json); + void OnCommandMessageUpdate(const JSONNode &json); + void OnCommandMessageAck(const JSONNode &json); + void OnCommandPresence(const JSONNode &json); + void OnCommandReady(const JSONNode &json); + void OnCommandRoleCreated(const JSONNode &json); + void OnCommandRoleDeleted(const JSONNode &json); + void OnCommandTyping(const JSONNode &json); + void OnCommandUserUpdate(const JSONNode &json); + void OnCommandUserSettingsUpdate(const JSONNode &json); + + void OnLoggedIn(); + void OnLoggedOut(); + + void OnReceiveCreateChannel(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveFile(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveMarkRead(NETLIBHTTPREQUEST *, AsyncHttpRequest *); + void OnReceiveMessageAck(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveToken(NETLIBHTTPREQUEST *, AsyncHttpRequest *); + void OnReceiveUserinfo(NETLIBHTTPREQUEST *, AsyncHttpRequest *); + + void RetrieveMyInfo(); + void OnReceiveMyInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + void RetrieveHistory(CDiscordUser *pUser, CDiscordHistoryOp iOp = MSG_NOFILTER, SnowFlake msgid = 0, int iLimit = 50); + void OnReceiveHistory(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + bool RetrieveAvatar(MCONTACT hContact); + void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + void OnSendMsg(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + ////////////////////////////////////////////////////////////////////////////////////// + // Misc + + void SendMarkRead(void); + void SetServerStatus(int iStatus); + void RemoveFriend(SnowFlake id); + + CMStringW GetAvatarFilename(MCONTACT hContact); + void CheckAvatarChange(MCONTACT hContact, const CMStringW &wszNewHash); +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct CMPlugin : public ACCPROTOPLUGIN +{ + CMPlugin(); + + bool bVoiceService = false; + + int Load() override; +}; diff --git a/protocols/Discord/src/resource.h b/protocols/Discord/src/resource.h index d0326e6857..099a4af3af 100644 --- a/protocols/Discord/src/resource.h +++ b/protocols/Discord/src/resource.h @@ -1,30 +1,30 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by w:\miranda-ng\protocols\Discord\res\discord.rc -// -#define IDI_MAIN 101 -#define IDI_GROUPCHAT 102 -#define IDD_OPTIONS_ACCOUNT 103 -#define IDD_EXTSEARCH 104 -#define IDD_OPTIONS_ACCMGR 105 -#define IDI_VOICE_CALL 106 -#define IDI_VOICE_ENDED 107 -#define IDC_PASSWORD 1001 -#define IDC_USERNAME 1002 -#define IDC_GROUP 1003 -#define IDC_NICK 1004 -#define IDC_HIDECHATS 1005 -#define IDC_USEGROUPS 1006 -#define IDC_USEGUILDS 1007 -#define IDC_DELETE_MSGS 1009 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 104 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1008 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by w:\miranda-ng\protocols\Discord\res\discord.rc +// +#define IDI_MAIN 101 +#define IDI_GROUPCHAT 102 +#define IDD_OPTIONS_ACCOUNT 103 +#define IDD_EXTSEARCH 104 +#define IDD_OPTIONS_ACCMGR 105 +#define IDI_VOICE_CALL 106 +#define IDI_VOICE_ENDED 107 +#define IDC_PASSWORD 1001 +#define IDC_USERNAME 1002 +#define IDC_GROUP 1003 +#define IDC_NICK 1004 +#define IDC_HIDECHATS 1005 +#define IDC_USEGROUPS 1006 +#define IDC_USEGUILDS 1007 +#define IDC_DELETE_MSGS 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp index cc6dfe2280..16f716e89f 100644 --- a/protocols/Discord/src/server.cpp +++ b/protocols/Discord/src/server.cpp @@ -1,307 +1,307 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// removes a friend from the server - -void CDiscordProto::RemoveFriend(SnowFlake id) -{ - Push(new AsyncHttpRequest(this, REQUEST_DELETE, CMStringA(FORMAT, "/users/@me/relationships/%lld", id), nullptr)); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieves server history - -void CDiscordProto::RetrieveHistory(CDiscordUser *pUser, CDiscordHistoryOp iOp, SnowFlake msgid, int iLimit) -{ - if (!pUser->hContact || getByte(pUser->hContact, DB_KEY_DONT_FETCH)) - return; - - CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId); - AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveHistory); - pReq << INT_PARAM("limit", iLimit); - - if (msgid) { - switch (iOp) { - case MSG_AFTER: - pReq << INT64_PARAM("after", msgid); break; - case MSG_BEFORE: - pReq << INT64_PARAM("before", msgid); break; - } - } - pReq->pUserInfo = pUser; - Push(pReq); -} - -static int compareMsgHistory(const JSONNode *p1, const JSONNode *p2) -{ - return wcscmp((*p1)["id"].as_mstring(), (*p2)["id"].as_mstring()); -} - -void CDiscordProto::OnReceiveHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - CDiscordUser *pUser = (CDiscordUser*)pReq->pUserInfo; - - JsonReply root(pReply); - if (!root) { - if (root.error() == 403) // forbidden, don't try to read it anymore - setByte(pUser->hContact, DB_KEY_DONT_FETCH, true); - return; - } - - SESSION_INFO *si = nullptr; - if (!pUser->bIsPrivate) { - si = g_chatApi.SM_FindSession(pUser->wszUsername, m_szModuleName); - if (si == nullptr) { - debugLogA("message to unknown channel %lld ignored", pUser->channelId); - return; - } - } - - SnowFlake lastId = getId(pUser->hContact, DB_KEY_LASTMSGID); // as stored in a database - - LIST arNodes(10, compareMsgHistory); - int iNumMessages = 0; - for (auto &it : root.data()) { - arNodes.insert(&it); - iNumMessages++; - } - - for (auto &it : arNodes) { - auto &pNode = *it; - CMStringW wszText = PrepareMessageText(pNode); - CMStringW wszUserId = pNode["author"]["id"].as_mstring(); - SnowFlake msgid = ::getId(pNode["id"]); - SnowFlake authorid = _wtoi64(wszUserId); - uint32_t dwTimeStamp = StringToDate(pNode["timestamp"].as_mstring()); - - if (pUser->bIsPrivate) { - DBEVENTINFO dbei = {}; - dbei.szModule = m_szModuleName; - dbei.flags = DBEF_UTF; - dbei.eventType = EVENTTYPE_MESSAGE; - - if (authorid == m_ownId) - dbei.flags |= DBEF_SENT; - else - dbei.flags &= ~DBEF_SENT; - - if (msgid <= pUser->lastReadId) - dbei.flags |= DBEF_READ; - else - dbei.flags &= ~DBEF_READ; - - ptrA szBody(mir_utf8encodeW(wszText)); - dbei.timestamp = dwTimeStamp; - dbei.pBlob = (uint8_t*)szBody.get(); - dbei.cbBlob = (uint32_t)mir_strlen(szBody); - - bool bSucceeded = false; - char szMsgId[100]; - _i64toa_s(msgid, szMsgId, _countof(szMsgId), 10); - MEVENT hDbEvent = db_event_getById(m_szModuleName, szMsgId); - if (hDbEvent != 0) - bSucceeded = 0 == db_event_edit(pUser->hContact, hDbEvent, &dbei); - - if (!bSucceeded) { - dbei.szId = szMsgId; - db_event_add(pUser->hContact, &dbei); - } - } - else { - ProcessChatUser(pUser, wszUserId, pNode); - - ParseSpecialChars(si, wszText); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE }; - gce.pszID.w = pUser->wszUsername; - gce.dwFlags = GCEF_ADDTOLOG; - gce.pszUID.w = wszUserId; - gce.pszText.w = wszText; - gce.time = dwTimeStamp; - gce.bIsMe = authorid == m_ownId; - Chat_Event(&gce); - } - - if (lastId < msgid) - lastId = msgid; - } - - setId(pUser->hContact, DB_KEY_LASTMSGID, lastId); - - // if we fetched 99 messages, but have smth more to go, continue fetching - if (iNumMessages == 99 && lastId < pUser->lastMsgId) - RetrieveHistory(pUser, MSG_AFTER, lastId, 99); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieves user info - -void CDiscordProto::RetrieveMyInfo() -{ - Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me", &CDiscordProto::OnReceiveMyInfo)); -} - -void CDiscordProto::OnReceiveMyInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (!root) { - ConnectionFailed(LOGINERR_WRONGPASSWORD); - return; - } - - auto &data = root.data(); - SnowFlake id = ::getId(data["id"]); - setId(0, DB_KEY_ID, id); - - setByte(0, DB_KEY_MFA, data["mfa_enabled"].as_bool()); - setDword(0, DB_KEY_DISCR, _wtoi(data["discriminator"].as_mstring())); - setWString(0, DB_KEY_NICK, data["username"].as_mstring()); - m_wszEmail = data["email"].as_mstring(); - - m_ownId = id; - - m_szCookie.Empty(); - for (int i=0; i < pReply->headersCount; i++) { - if (!mir_strcmpi(pReply->headers[i].szName, "Set-Cookie")) { - char *p = strchr(pReply->headers[i].szValue, ';'); - if (p) *p = 0; - if (!m_szCookie.IsEmpty()) - m_szCookie.Append("; "); - - m_szCookie.Append(pReply->headers[i].szValue); - } - } - - // launch gateway thread - if (m_szGateway.IsEmpty()) - Push(new AsyncHttpRequest(this, REQUEST_GET, "/gateway", &CDiscordProto::OnReceiveGateway)); - else - ForkThread(&CDiscordProto::GatewayThread, nullptr); - - CheckAvatarChange(0, data["avatar"].as_mstring()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// finds a gateway address - -void CDiscordProto::OnReceiveGateway(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (!root) { - ShutdownSession(); - return; - } - - auto &data = root.data(); - m_szGateway = data["url"].as_mstring(); - ForkThread(&CDiscordProto::GatewayThread, nullptr); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::SetServerStatus(int iStatus) -{ - if (GatewaySendStatus(iStatus, nullptr)) { - int iOldStatus = m_iStatus; m_iStatus = iStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// channels - -void CDiscordProto::OnReceiveCreateChannel(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (root) - OnCommandChannelCreated(root.data()); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::OnReceiveMessageAck(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (!root) - return; - - auto &data = root.data(); - CMStringW wszToken(data["token"].as_mstring()); - if (!wszToken.IsEmpty()) { - JSONNode props; props.set_name("properties"); - JSONNode reply; reply << props; - reply << CHAR_PARAM("event", "ack_messages") << WCHAR_PARAM("token", data["token"].as_mstring()); - Push(new AsyncHttpRequest(this, REQUEST_POST, "/track", nullptr, &reply)); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define RECAPTCHA_API_KEY "6Lef5iQTAAAAAKeIvIY-DeexoO3gj7ryl9rLMEnn" -#define RECAPTCHA_SITE_URL "https://discord.com" - -void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - if (pReply->resultCode != 200) { - JSONNode root = JSONNode::parse(pReply->pData); - if (root) { - const JSONNode &captcha = root["captcha_key"].as_array(); - if (captcha) { - for (auto &it : captcha) { - if (it.as_mstring() == "captcha-required") { - MessageBoxW(NULL, TranslateT("The server requires you to enter the captcha. Miranda will redirect you to a browser now"), L"Discord", MB_OK | MB_ICONINFORMATION); - Utils_OpenUrl("https://discord.com/app"); - } - } - } - - for (auto &err: root["errors"]["email"]["_errors"]) { - CMStringW code(err["code"].as_mstring()); - CMStringW message(err["message"].as_mstring()); - if (!code.IsEmpty() || !message.IsEmpty()) { - POPUPDATAW popup; - popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true); - wcscpy_s(popup.lpwzContactName, m_tszUserName); - mir_snwprintf(popup.lpwzText, TranslateT("Connection failed.\n%s (%s)."), message.c_str(), code.c_str()); - PUAddPopupW(&popup); - } - } - } - ConnectionFailed(LOGINERR_WRONGPASSWORD); - return; - } - - JsonReply root(pReply); - if (!root) { - ConnectionFailed(LOGINERR_NOSERVER); - return; - } - - auto &data = root.data(); - CMStringA szToken = data["token"].as_mstring(); - if (szToken.IsEmpty()) { - debugLogA("Strange empty token received, exiting"); - return; - } - - m_szAccessToken = szToken.Detach(); - setString("AccessToken", m_szAccessToken); - RetrieveMyInfo(); -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// +// removes a friend from the server + +void CDiscordProto::RemoveFriend(SnowFlake id) +{ + Push(new AsyncHttpRequest(this, REQUEST_DELETE, CMStringA(FORMAT, "/users/@me/relationships/%lld", id), nullptr)); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieves server history + +void CDiscordProto::RetrieveHistory(CDiscordUser *pUser, CDiscordHistoryOp iOp, SnowFlake msgid, int iLimit) +{ + if (!pUser->hContact || getByte(pUser->hContact, DB_KEY_DONT_FETCH)) + return; + + CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveHistory); + pReq << INT_PARAM("limit", iLimit); + + if (msgid) { + switch (iOp) { + case MSG_AFTER: + pReq << INT64_PARAM("after", msgid); break; + case MSG_BEFORE: + pReq << INT64_PARAM("before", msgid); break; + } + } + pReq->pUserInfo = pUser; + Push(pReq); +} + +static int compareMsgHistory(const JSONNode *p1, const JSONNode *p2) +{ + return wcscmp((*p1)["id"].as_mstring(), (*p2)["id"].as_mstring()); +} + +void CDiscordProto::OnReceiveHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + CDiscordUser *pUser = (CDiscordUser*)pReq->pUserInfo; + + JsonReply root(pReply); + if (!root) { + if (root.error() == 403) // forbidden, don't try to read it anymore + setByte(pUser->hContact, DB_KEY_DONT_FETCH, true); + return; + } + + SESSION_INFO *si = nullptr; + if (!pUser->bIsPrivate) { + si = g_chatApi.SM_FindSession(pUser->wszUsername, m_szModuleName); + if (si == nullptr) { + debugLogA("message to unknown channel %lld ignored", pUser->channelId); + return; + } + } + + SnowFlake lastId = getId(pUser->hContact, DB_KEY_LASTMSGID); // as stored in a database + + LIST arNodes(10, compareMsgHistory); + int iNumMessages = 0; + for (auto &it : root.data()) { + arNodes.insert(&it); + iNumMessages++; + } + + for (auto &it : arNodes) { + auto &pNode = *it; + CMStringW wszText = PrepareMessageText(pNode); + CMStringW wszUserId = pNode["author"]["id"].as_mstring(); + SnowFlake msgid = ::getId(pNode["id"]); + SnowFlake authorid = _wtoi64(wszUserId); + uint32_t dwTimeStamp = StringToDate(pNode["timestamp"].as_mstring()); + + if (pUser->bIsPrivate) { + DBEVENTINFO dbei = {}; + dbei.szModule = m_szModuleName; + dbei.flags = DBEF_UTF; + dbei.eventType = EVENTTYPE_MESSAGE; + + if (authorid == m_ownId) + dbei.flags |= DBEF_SENT; + else + dbei.flags &= ~DBEF_SENT; + + if (msgid <= pUser->lastReadId) + dbei.flags |= DBEF_READ; + else + dbei.flags &= ~DBEF_READ; + + ptrA szBody(mir_utf8encodeW(wszText)); + dbei.timestamp = dwTimeStamp; + dbei.pBlob = (uint8_t*)szBody.get(); + dbei.cbBlob = (uint32_t)mir_strlen(szBody); + + bool bSucceeded = false; + char szMsgId[100]; + _i64toa_s(msgid, szMsgId, _countof(szMsgId), 10); + MEVENT hDbEvent = db_event_getById(m_szModuleName, szMsgId); + if (hDbEvent != 0) + bSucceeded = 0 == db_event_edit(pUser->hContact, hDbEvent, &dbei); + + if (!bSucceeded) { + dbei.szId = szMsgId; + db_event_add(pUser->hContact, &dbei); + } + } + else { + ProcessChatUser(pUser, wszUserId, pNode); + + ParseSpecialChars(si, wszText); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE }; + gce.pszID.w = pUser->wszUsername; + gce.dwFlags = GCEF_ADDTOLOG; + gce.pszUID.w = wszUserId; + gce.pszText.w = wszText; + gce.time = dwTimeStamp; + gce.bIsMe = authorid == m_ownId; + Chat_Event(&gce); + } + + if (lastId < msgid) + lastId = msgid; + } + + setId(pUser->hContact, DB_KEY_LASTMSGID, lastId); + + // if we fetched 99 messages, but have smth more to go, continue fetching + if (iNumMessages == 99 && lastId < pUser->lastMsgId) + RetrieveHistory(pUser, MSG_AFTER, lastId, 99); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieves user info + +void CDiscordProto::RetrieveMyInfo() +{ + Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me", &CDiscordProto::OnReceiveMyInfo)); +} + +void CDiscordProto::OnReceiveMyInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + JsonReply root(pReply); + if (!root) { + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return; + } + + auto &data = root.data(); + SnowFlake id = ::getId(data["id"]); + setId(0, DB_KEY_ID, id); + + setByte(0, DB_KEY_MFA, data["mfa_enabled"].as_bool()); + setDword(0, DB_KEY_DISCR, _wtoi(data["discriminator"].as_mstring())); + setWString(0, DB_KEY_NICK, data["username"].as_mstring()); + m_wszEmail = data["email"].as_mstring(); + + m_ownId = id; + + m_szCookie.Empty(); + for (int i=0; i < pReply->headersCount; i++) { + if (!mir_strcmpi(pReply->headers[i].szName, "Set-Cookie")) { + char *p = strchr(pReply->headers[i].szValue, ';'); + if (p) *p = 0; + if (!m_szCookie.IsEmpty()) + m_szCookie.Append("; "); + + m_szCookie.Append(pReply->headers[i].szValue); + } + } + + // launch gateway thread + if (m_szGateway.IsEmpty()) + Push(new AsyncHttpRequest(this, REQUEST_GET, "/gateway", &CDiscordProto::OnReceiveGateway)); + else + ForkThread(&CDiscordProto::GatewayThread, nullptr); + + CheckAvatarChange(0, data["avatar"].as_mstring()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// finds a gateway address + +void CDiscordProto::OnReceiveGateway(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + JsonReply root(pReply); + if (!root) { + ShutdownSession(); + return; + } + + auto &data = root.data(); + m_szGateway = data["url"].as_mstring(); + ForkThread(&CDiscordProto::GatewayThread, nullptr); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::SetServerStatus(int iStatus) +{ + if (GatewaySendStatus(iStatus, nullptr)) { + int iOldStatus = m_iStatus; m_iStatus = iStatus; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// channels + +void CDiscordProto::OnReceiveCreateChannel(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + JsonReply root(pReply); + if (root) + OnCommandChannelCreated(root.data()); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnReceiveMessageAck(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + JsonReply root(pReply); + if (!root) + return; + + auto &data = root.data(); + CMStringW wszToken(data["token"].as_mstring()); + if (!wszToken.IsEmpty()) { + JSONNode props; props.set_name("properties"); + JSONNode reply; reply << props; + reply << CHAR_PARAM("event", "ack_messages") << WCHAR_PARAM("token", data["token"].as_mstring()); + Push(new AsyncHttpRequest(this, REQUEST_POST, "/track", nullptr, &reply)); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#define RECAPTCHA_API_KEY "6Lef5iQTAAAAAKeIvIY-DeexoO3gj7ryl9rLMEnn" +#define RECAPTCHA_SITE_URL "https://discord.com" + +void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + if (pReply->resultCode != 200) { + JSONNode root = JSONNode::parse(pReply->pData); + if (root) { + const JSONNode &captcha = root["captcha_key"].as_array(); + if (captcha) { + for (auto &it : captcha) { + if (it.as_mstring() == "captcha-required") { + MessageBoxW(NULL, TranslateT("The server requires you to enter the captcha. Miranda will redirect you to a browser now"), L"Discord", MB_OK | MB_ICONINFORMATION); + Utils_OpenUrl("https://discord.com/app"); + } + } + } + + for (auto &err: root["errors"]["email"]["_errors"]) { + CMStringW code(err["code"].as_mstring()); + CMStringW message(err["message"].as_mstring()); + if (!code.IsEmpty() || !message.IsEmpty()) { + POPUPDATAW popup; + popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true); + wcscpy_s(popup.lpwzContactName, m_tszUserName); + mir_snwprintf(popup.lpwzText, TranslateT("Connection failed.\n%s (%s)."), message.c_str(), code.c_str()); + PUAddPopupW(&popup); + } + } + } + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return; + } + + JsonReply root(pReply); + if (!root) { + ConnectionFailed(LOGINERR_NOSERVER); + return; + } + + auto &data = root.data(); + CMStringA szToken = data["token"].as_mstring(); + if (szToken.IsEmpty()) { + debugLogA("Strange empty token received, exiting"); + return; + } + + m_szAccessToken = szToken.Detach(); + setString("AccessToken", m_szAccessToken); + RetrieveMyInfo(); +} diff --git a/protocols/Discord/src/stdafx.cxx b/protocols/Discord/src/stdafx.cxx index 4b7f53343f..52b06cb953 100644 --- a/protocols/Discord/src/stdafx.cxx +++ b/protocols/Discord/src/stdafx.cxx @@ -1,18 +1,18 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + #include "stdafx.h" \ No newline at end of file diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h index 6cba015cc3..48d68292dd 100644 --- a/protocols/Discord/src/stdafx.h +++ b/protocols/Discord/src/stdafx.h @@ -1,80 +1,80 @@ -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "resource.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../libs/zlib/src/zlib.h" - -extern IconItem g_iconList[]; - -#define DB_KEY_ID "id" -#define DB_KEY_PASSWORD "Password" -#define DB_KEY_DISCR "Discriminator" -#define DB_KEY_MFA "MfaEnabled" -#define DB_KEY_NICK "Nick" -#define DB_KEY_AVHASH "AvatarHash" -#define DB_KEY_CHANNELID "ChannelID" -#define DB_KEY_LASTMSGID "LastMessageID" -#define DB_KEY_REQAUTH "ReqAuth" -#define DB_KEY_DONT_FETCH "DontFetch" - -#define DB_KEYVAL_GROUP L"Discord" - -#include "version.h" -#include "proto.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -void BuildStatusList(const CDiscordGuild *pGuild, SESSION_INFO *si); - -void CopyId(const CMStringW &nick); -SnowFlake getId(const JSONNode &pNode); -CMStringW PrepareMessageText(const JSONNode &pRoot); -int StrToStatus(const CMStringW &str); -time_t StringToDate(const CMStringW &str); -int SerialNext(void); +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "resource.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../libs/zlib/src/zlib.h" + +extern IconItem g_iconList[]; + +#define DB_KEY_ID "id" +#define DB_KEY_PASSWORD "Password" +#define DB_KEY_DISCR "Discriminator" +#define DB_KEY_MFA "MfaEnabled" +#define DB_KEY_NICK "Nick" +#define DB_KEY_AVHASH "AvatarHash" +#define DB_KEY_CHANNELID "ChannelID" +#define DB_KEY_LASTMSGID "LastMessageID" +#define DB_KEY_REQAUTH "ReqAuth" +#define DB_KEY_DONT_FETCH "DontFetch" + +#define DB_KEYVAL_GROUP L"Discord" + +#include "version.h" +#include "proto.h" + +///////////////////////////////////////////////////////////////////////////////////////// + +void BuildStatusList(const CDiscordGuild *pGuild, SESSION_INFO *si); + +void CopyId(const CMStringW &nick); +SnowFlake getId(const JSONNode &pNode); +CMStringW PrepareMessageText(const JSONNode &pRoot); +int StrToStatus(const CMStringW &str); +time_t StringToDate(const CMStringW &str); +int SerialNext(void); diff --git a/protocols/Discord/src/utils.cpp b/protocols/Discord/src/utils.cpp index ac40407c69..ce12a81443 100644 --- a/protocols/Discord/src/utils.cpp +++ b/protocols/Discord/src/utils.cpp @@ -1,376 +1,376 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -int StrToStatus(const CMStringW &str) -{ - if (str == L"idle") - return ID_STATUS_NA; - if (str == L"dnd") - return ID_STATUS_DND; - if (str == L"online") - return ID_STATUS_ONLINE; - if (str == L"offline") - return ID_STATUS_OFFLINE; - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -time_t StringToDate(const CMStringW &str) -{ - struct tm T = { 0 }; - int boo; - if (swscanf(str, L"%04d-%02d-%02dT%02d:%02d:%02d.%d", &T.tm_year, &T.tm_mon, &T.tm_mday, &T.tm_hour, &T.tm_min, &T.tm_sec, &boo) != 7) - return time(0); - - T.tm_year -= 1900; - T.tm_mon--; - time_t t = mktime(&T); - - _tzset(); - t -= _timezone; - return (t >= 0) ? t : 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static LONG volatile g_counter = 1; - -int SerialNext() -{ - return InterlockedIncrement(&g_counter); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -SnowFlake getId(const JSONNode &pNode) -{ - return _wtoi64(pNode.as_mstring()); -} - -SnowFlake CDiscordProto::getId(const char *szSetting) -{ - DBVARIANT dbv; - dbv.type = DBVT_BLOB; - if (db_get(0, m_szModuleName, szSetting, &dbv)) - return 0; - - SnowFlake result = (dbv.cpbVal == sizeof(SnowFlake)) ? *(SnowFlake*)dbv.pbVal : 0; - db_free(&dbv); - return result; -} - -SnowFlake CDiscordProto::getId(MCONTACT hContact, const char *szSetting) -{ - DBVARIANT dbv; - dbv.type = DBVT_BLOB; - if (db_get(hContact, m_szModuleName, szSetting, &dbv)) - return 0; - - SnowFlake result = (dbv.cpbVal == sizeof(SnowFlake)) ? *(SnowFlake*)dbv.pbVal : 0; - db_free(&dbv); - return result; -} - -void CDiscordProto::setId(const char *szSetting, SnowFlake iValue) -{ - SnowFlake oldVal = getId(szSetting); - if (oldVal != iValue) - db_set_blob(0, m_szModuleName, szSetting, &iValue, sizeof(iValue)); -} - -void CDiscordProto::setId(MCONTACT hContact, const char *szSetting, SnowFlake iValue) -{ - SnowFlake oldVal = getId(hContact, szSetting); - if (oldVal != iValue) - db_set_blob(hContact, m_szModuleName, szSetting, &iValue, sizeof(iValue)); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CopyId(const CMStringW &nick) -{ - if (!OpenClipboard(nullptr)) - return; - - EmptyClipboard(); - - int length = nick.GetLength() + 1; - if (HGLOBAL hMemory = GlobalAlloc(GMEM_FIXED, length * sizeof(wchar_t))) { - mir_wstrncpy((wchar_t*)GlobalLock(hMemory), nick, length); - GlobalUnlock(hMemory); - SetClipboardData(CF_UNICODETEXT, hMemory); - } - CloseClipboard(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static CDiscordUser *g_myUser = new CDiscordUser(0); - -CDiscordUser* CDiscordProto::FindUser(SnowFlake id) -{ - return arUsers.find((CDiscordUser*)&id); -} - -CDiscordUser* CDiscordProto::FindUser(const wchar_t *pwszUsername, int iDiscriminator) -{ - for (auto &p : arUsers) - if (p->wszUsername == pwszUsername && p->iDiscriminator == iDiscriminator) - return p; - - return nullptr; -} - -CDiscordUser* CDiscordProto::FindUserByChannel(SnowFlake channelId) -{ - for (auto &p : arUsers) - if (p->channelId == channelId) - return p; - - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Common JSON processing routines - -void CDiscordProto::PreparePrivateChannel(const JSONNode &root) -{ - CDiscordUser *pUser = nullptr; - - CMStringW wszChannelId = root["id"].as_mstring(); - SnowFlake channelId = _wtoi64(wszChannelId); - - int type = root["type"].as_int(); - switch (type) { - case 1: // single channel - for (auto &it : root["recipients"]) - pUser = PrepareUser(it); - if (pUser == nullptr) { - debugLogA("Invalid recipients list, exiting"); - return; - } - break; - - case 3: // private groupchat - if ((pUser = FindUserByChannel(channelId)) == nullptr) { - pUser = new CDiscordUser(channelId); - arUsers.insert(pUser); - } - pUser->bIsGroup = true; - pUser->wszUsername = wszChannelId; - pUser->wszChannelName = root["name"].as_mstring(); - { - SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, pUser->wszUsername, pUser->wszChannelName); - pUser->hContact = si->hContact; - - Chat_AddGroup(si, LPGENW("Owners")); - Chat_AddGroup(si, LPGENW("Participants")); - - SnowFlake ownerId = _wtoi64(root["owner_id"].as_mstring()); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN }; - gce.pszID.w = pUser->wszUsername; - for (auto &it : root["recipients"]) { - CMStringW wszId = it["id"].as_mstring(); - CMStringW wszNick = it["nick"].as_mstring(); - if (wszNick.IsEmpty()) - wszNick = it["username"].as_mstring() + L"#" + it["discriminator"].as_mstring(); - - gce.pszUID.w = wszId; - gce.pszNick.w = wszNick; - gce.pszStatus.w = (_wtoi64(wszId) == ownerId) ? L"Owners" : L"Participants"; - Chat_Event(&gce); - } - - CMStringW wszId(FORMAT, L"%lld", getId(DB_KEY_ID)); - CMStringW wszNick(FORMAT, L"%s#%d", getMStringW(DB_KEY_NICK).c_str(), getDword(DB_KEY_DISCR)); - gce.bIsMe = true; - gce.pszUID.w = wszId; - gce.pszNick.w = wszNick; - gce.pszStatus.w = (_wtoi64(wszId) == ownerId) ? L"Owners" : L"Participants"; - Chat_Event(&gce); - - Chat_Control(m_szModuleName, pUser->wszUsername, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); - Chat_Control(m_szModuleName, pUser->wszUsername, SESSION_ONLINE); - } - break; - - default: - debugLogA("Invalid channel type: %d, exiting", type); - return; - } - - pUser->channelId = channelId; - pUser->lastMsgId = ::getId(root["last_message_id"]); - pUser->bIsPrivate = true; - - setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId); - - SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID); - if (pUser->lastMsgId > oldMsgId) - RetrieveHistory(pUser, MSG_AFTER, oldMsgId, 99); -} - -CDiscordUser* CDiscordProto::PrepareUser(const JSONNode &user) -{ - SnowFlake id = ::getId(user["id"]); - if (id == m_ownId) - return g_myUser; - - int iDiscriminator = _wtoi(user["discriminator"].as_mstring()); - CMStringW username = user["username"].as_mstring(); - - CDiscordUser *pUser = FindUser(id); - if (pUser == nullptr) { - MCONTACT tmp = INVALID_CONTACT_ID; - - // no user found by userid, try to find him via username+discriminator - pUser = FindUser(username, iDiscriminator); - if (pUser != nullptr) { - // if found, remove the object from list to resort it (its userid==0) - if (pUser->hContact != 0) - tmp = pUser->hContact; - arUsers.remove(pUser); - } - pUser = new CDiscordUser(id); - pUser->wszUsername = username; - pUser->iDiscriminator = iDiscriminator; - if (tmp != INVALID_CONTACT_ID) { - // if we previously had a recently added contact without userid, write it down - pUser->hContact = tmp; - setId(pUser->hContact, DB_KEY_ID, id); - } - arUsers.insert(pUser); - } - - if (pUser->hContact == 0) { - MCONTACT hContact = db_add_contact(); - Proto_AddToContact(hContact, m_szModuleName); - - Clist_SetGroup(hContact, m_wszDefaultGroup); - setId(hContact, DB_KEY_ID, id); - setWString(hContact, DB_KEY_NICK, username); - setDword(hContact, DB_KEY_DISCR, iDiscriminator); - - pUser->hContact = hContact; - } - - CheckAvatarChange(pUser->hContact, user["avatar"].as_mstring()); - return pUser; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringW PrepareMessageText(const JSONNode &pRoot) -{ - CMStringW wszText = pRoot["content"].as_mstring(); - - bool bDelimiterAdded = false; - for (auto &it : pRoot["attachments"]) { - CMStringW wszUrl = it["url"].as_mstring(); - if (!wszUrl.IsEmpty()) { - if (!bDelimiterAdded) { - bDelimiterAdded = true; - wszText.Append(L"\n-----------------"); - } - wszText.AppendFormat(L"\n%s: %s", TranslateT("Attachment"), wszUrl.c_str()); - } - } - - for (auto &it : pRoot["embeds"]) { - wszText.Append(L"\n-----------------"); - - CMStringW str = it["url"].as_mstring(); - wszText.AppendFormat(L"\n%s: %s", TranslateT("Embed"), str.c_str()); - - str = it["provider"]["name"].as_mstring() + L" " + it["type"].as_mstring(); - if (str.GetLength() > 1) - wszText.AppendFormat(L"\n\t%s", str.c_str()); - - str = it["description"].as_mstring(); - if (!str.IsEmpty()) - wszText.AppendFormat(L"\n\t%s", str.c_str()); - - str = it["thumbnail"]["url"].as_mstring(); - if (!str.IsEmpty()) - wszText.AppendFormat(L"\n%s: %s", TranslateT("Preview"), str.c_str()); - } - - return wszText; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::ProcessType(CDiscordUser *pUser, const JSONNode &pRoot) -{ - switch (pRoot["type"].as_int()) { - case 1: // confirmed - Contact::PutOnList(pUser->hContact); - delSetting(pUser->hContact, DB_KEY_REQAUTH); - delSetting(pUser->hContact, "ApparentMode"); - break; - - case 3: // expecting authorization - Contact::RemoveFromList(pUser->hContact); - if (!getByte(pUser->hContact, DB_KEY_REQAUTH, 0)) { - setByte(pUser->hContact, DB_KEY_REQAUTH, 1); - - CMStringA szId(FORMAT, "%lld", pUser->id); - DB::AUTH_BLOB blob(pUser->hContact, T2Utf(pUser->wszUsername), nullptr, nullptr, szId, nullptr); - - PROTORECVEVENT pre = { 0 }; - pre.timestamp = (uint32_t)time(0); - pre.lParam = blob.size(); - pre.szMessage = blob; - ProtoChainRecv(pUser->hContact, PSR_AUTH, 0, (LPARAM)&pre); - } - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CDiscordProto::ParseSpecialChars(SESSION_INFO *si, CMStringW &str) -{ - for (int i = 0; (i = str.Find('<', i)) != -1; i++) { - int iEnd = str.Find('>', i + 1); - if (iEnd == -1) - return; - - CMStringW wszWord = str.Mid(i + 1, iEnd - i - 1); - if (wszWord[0] == '@') { // member highlight - int iStart = 1; - if (wszWord[1] == '!') - iStart++; - - USERINFO *ui = g_chatApi.UM_FindUser(si, wszWord.c_str() + iStart); - if (ui != nullptr) - str.Replace(L"<" + wszWord + L">", CMStringW(ui->pszNick) + L": "); - } - else if (wszWord[0] == '#') { - CDiscordUser *pUser = FindUserByChannel(_wtoi64(wszWord.c_str() + 1)); - if (pUser != nullptr) { - ptrW wszNick(getWStringA(pUser->hContact, DB_KEY_NICK)); - if (wszNick != nullptr) - str.Replace(L"<" + wszWord + L">", wszNick); - } - } - } -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +int StrToStatus(const CMStringW &str) +{ + if (str == L"idle") + return ID_STATUS_NA; + if (str == L"dnd") + return ID_STATUS_DND; + if (str == L"online") + return ID_STATUS_ONLINE; + if (str == L"offline") + return ID_STATUS_OFFLINE; + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +time_t StringToDate(const CMStringW &str) +{ + struct tm T = { 0 }; + int boo; + if (swscanf(str, L"%04d-%02d-%02dT%02d:%02d:%02d.%d", &T.tm_year, &T.tm_mon, &T.tm_mday, &T.tm_hour, &T.tm_min, &T.tm_sec, &boo) != 7) + return time(0); + + T.tm_year -= 1900; + T.tm_mon--; + time_t t = mktime(&T); + + _tzset(); + t -= _timezone; + return (t >= 0) ? t : 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static LONG volatile g_counter = 1; + +int SerialNext() +{ + return InterlockedIncrement(&g_counter); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +SnowFlake getId(const JSONNode &pNode) +{ + return _wtoi64(pNode.as_mstring()); +} + +SnowFlake CDiscordProto::getId(const char *szSetting) +{ + DBVARIANT dbv; + dbv.type = DBVT_BLOB; + if (db_get(0, m_szModuleName, szSetting, &dbv)) + return 0; + + SnowFlake result = (dbv.cpbVal == sizeof(SnowFlake)) ? *(SnowFlake*)dbv.pbVal : 0; + db_free(&dbv); + return result; +} + +SnowFlake CDiscordProto::getId(MCONTACT hContact, const char *szSetting) +{ + DBVARIANT dbv; + dbv.type = DBVT_BLOB; + if (db_get(hContact, m_szModuleName, szSetting, &dbv)) + return 0; + + SnowFlake result = (dbv.cpbVal == sizeof(SnowFlake)) ? *(SnowFlake*)dbv.pbVal : 0; + db_free(&dbv); + return result; +} + +void CDiscordProto::setId(const char *szSetting, SnowFlake iValue) +{ + SnowFlake oldVal = getId(szSetting); + if (oldVal != iValue) + db_set_blob(0, m_szModuleName, szSetting, &iValue, sizeof(iValue)); +} + +void CDiscordProto::setId(MCONTACT hContact, const char *szSetting, SnowFlake iValue) +{ + SnowFlake oldVal = getId(hContact, szSetting); + if (oldVal != iValue) + db_set_blob(hContact, m_szModuleName, szSetting, &iValue, sizeof(iValue)); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CopyId(const CMStringW &nick) +{ + if (!OpenClipboard(nullptr)) + return; + + EmptyClipboard(); + + int length = nick.GetLength() + 1; + if (HGLOBAL hMemory = GlobalAlloc(GMEM_FIXED, length * sizeof(wchar_t))) { + mir_wstrncpy((wchar_t*)GlobalLock(hMemory), nick, length); + GlobalUnlock(hMemory); + SetClipboardData(CF_UNICODETEXT, hMemory); + } + CloseClipboard(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static CDiscordUser *g_myUser = new CDiscordUser(0); + +CDiscordUser* CDiscordProto::FindUser(SnowFlake id) +{ + return arUsers.find((CDiscordUser*)&id); +} + +CDiscordUser* CDiscordProto::FindUser(const wchar_t *pwszUsername, int iDiscriminator) +{ + for (auto &p : arUsers) + if (p->wszUsername == pwszUsername && p->iDiscriminator == iDiscriminator) + return p; + + return nullptr; +} + +CDiscordUser* CDiscordProto::FindUserByChannel(SnowFlake channelId) +{ + for (auto &p : arUsers) + if (p->channelId == channelId) + return p; + + return nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Common JSON processing routines + +void CDiscordProto::PreparePrivateChannel(const JSONNode &root) +{ + CDiscordUser *pUser = nullptr; + + CMStringW wszChannelId = root["id"].as_mstring(); + SnowFlake channelId = _wtoi64(wszChannelId); + + int type = root["type"].as_int(); + switch (type) { + case 1: // single channel + for (auto &it : root["recipients"]) + pUser = PrepareUser(it); + if (pUser == nullptr) { + debugLogA("Invalid recipients list, exiting"); + return; + } + break; + + case 3: // private groupchat + if ((pUser = FindUserByChannel(channelId)) == nullptr) { + pUser = new CDiscordUser(channelId); + arUsers.insert(pUser); + } + pUser->bIsGroup = true; + pUser->wszUsername = wszChannelId; + pUser->wszChannelName = root["name"].as_mstring(); + { + SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, pUser->wszUsername, pUser->wszChannelName); + pUser->hContact = si->hContact; + + Chat_AddGroup(si, LPGENW("Owners")); + Chat_AddGroup(si, LPGENW("Participants")); + + SnowFlake ownerId = _wtoi64(root["owner_id"].as_mstring()); + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN }; + gce.pszID.w = pUser->wszUsername; + for (auto &it : root["recipients"]) { + CMStringW wszId = it["id"].as_mstring(); + CMStringW wszNick = it["nick"].as_mstring(); + if (wszNick.IsEmpty()) + wszNick = it["username"].as_mstring() + L"#" + it["discriminator"].as_mstring(); + + gce.pszUID.w = wszId; + gce.pszNick.w = wszNick; + gce.pszStatus.w = (_wtoi64(wszId) == ownerId) ? L"Owners" : L"Participants"; + Chat_Event(&gce); + } + + CMStringW wszId(FORMAT, L"%lld", getId(DB_KEY_ID)); + CMStringW wszNick(FORMAT, L"%s#%d", getMStringW(DB_KEY_NICK).c_str(), getDword(DB_KEY_DISCR)); + gce.bIsMe = true; + gce.pszUID.w = wszId; + gce.pszNick.w = wszNick; + gce.pszStatus.w = (_wtoi64(wszId) == ownerId) ? L"Owners" : L"Participants"; + Chat_Event(&gce); + + Chat_Control(m_szModuleName, pUser->wszUsername, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); + Chat_Control(m_szModuleName, pUser->wszUsername, SESSION_ONLINE); + } + break; + + default: + debugLogA("Invalid channel type: %d, exiting", type); + return; + } + + pUser->channelId = channelId; + pUser->lastMsgId = ::getId(root["last_message_id"]); + pUser->bIsPrivate = true; + + setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId); + + SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID); + if (pUser->lastMsgId > oldMsgId) + RetrieveHistory(pUser, MSG_AFTER, oldMsgId, 99); +} + +CDiscordUser* CDiscordProto::PrepareUser(const JSONNode &user) +{ + SnowFlake id = ::getId(user["id"]); + if (id == m_ownId) + return g_myUser; + + int iDiscriminator = _wtoi(user["discriminator"].as_mstring()); + CMStringW username = user["username"].as_mstring(); + + CDiscordUser *pUser = FindUser(id); + if (pUser == nullptr) { + MCONTACT tmp = INVALID_CONTACT_ID; + + // no user found by userid, try to find him via username+discriminator + pUser = FindUser(username, iDiscriminator); + if (pUser != nullptr) { + // if found, remove the object from list to resort it (its userid==0) + if (pUser->hContact != 0) + tmp = pUser->hContact; + arUsers.remove(pUser); + } + pUser = new CDiscordUser(id); + pUser->wszUsername = username; + pUser->iDiscriminator = iDiscriminator; + if (tmp != INVALID_CONTACT_ID) { + // if we previously had a recently added contact without userid, write it down + pUser->hContact = tmp; + setId(pUser->hContact, DB_KEY_ID, id); + } + arUsers.insert(pUser); + } + + if (pUser->hContact == 0) { + MCONTACT hContact = db_add_contact(); + Proto_AddToContact(hContact, m_szModuleName); + + Clist_SetGroup(hContact, m_wszDefaultGroup); + setId(hContact, DB_KEY_ID, id); + setWString(hContact, DB_KEY_NICK, username); + setDword(hContact, DB_KEY_DISCR, iDiscriminator); + + pUser->hContact = hContact; + } + + CheckAvatarChange(pUser->hContact, user["avatar"].as_mstring()); + return pUser; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CMStringW PrepareMessageText(const JSONNode &pRoot) +{ + CMStringW wszText = pRoot["content"].as_mstring(); + + bool bDelimiterAdded = false; + for (auto &it : pRoot["attachments"]) { + CMStringW wszUrl = it["url"].as_mstring(); + if (!wszUrl.IsEmpty()) { + if (!bDelimiterAdded) { + bDelimiterAdded = true; + wszText.Append(L"\n-----------------"); + } + wszText.AppendFormat(L"\n%s: %s", TranslateT("Attachment"), wszUrl.c_str()); + } + } + + for (auto &it : pRoot["embeds"]) { + wszText.Append(L"\n-----------------"); + + CMStringW str = it["url"].as_mstring(); + wszText.AppendFormat(L"\n%s: %s", TranslateT("Embed"), str.c_str()); + + str = it["provider"]["name"].as_mstring() + L" " + it["type"].as_mstring(); + if (str.GetLength() > 1) + wszText.AppendFormat(L"\n\t%s", str.c_str()); + + str = it["description"].as_mstring(); + if (!str.IsEmpty()) + wszText.AppendFormat(L"\n\t%s", str.c_str()); + + str = it["thumbnail"]["url"].as_mstring(); + if (!str.IsEmpty()) + wszText.AppendFormat(L"\n%s: %s", TranslateT("Preview"), str.c_str()); + } + + return wszText; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::ProcessType(CDiscordUser *pUser, const JSONNode &pRoot) +{ + switch (pRoot["type"].as_int()) { + case 1: // confirmed + Contact::PutOnList(pUser->hContact); + delSetting(pUser->hContact, DB_KEY_REQAUTH); + delSetting(pUser->hContact, "ApparentMode"); + break; + + case 3: // expecting authorization + Contact::RemoveFromList(pUser->hContact); + if (!getByte(pUser->hContact, DB_KEY_REQAUTH, 0)) { + setByte(pUser->hContact, DB_KEY_REQAUTH, 1); + + CMStringA szId(FORMAT, "%lld", pUser->id); + DB::AUTH_BLOB blob(pUser->hContact, T2Utf(pUser->wszUsername), nullptr, nullptr, szId, nullptr); + + PROTORECVEVENT pre = { 0 }; + pre.timestamp = (uint32_t)time(0); + pre.lParam = blob.size(); + pre.szMessage = blob; + ProtoChainRecv(pUser->hContact, PSR_AUTH, 0, (LPARAM)&pre); + } + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::ParseSpecialChars(SESSION_INFO *si, CMStringW &str) +{ + for (int i = 0; (i = str.Find('<', i)) != -1; i++) { + int iEnd = str.Find('>', i + 1); + if (iEnd == -1) + return; + + CMStringW wszWord = str.Mid(i + 1, iEnd - i - 1); + if (wszWord[0] == '@') { // member highlight + int iStart = 1; + if (wszWord[1] == '!') + iStart++; + + USERINFO *ui = g_chatApi.UM_FindUser(si, wszWord.c_str() + iStart); + if (ui != nullptr) + str.Replace(L"<" + wszWord + L">", CMStringW(ui->pszNick) + L": "); + } + else if (wszWord[0] == '#') { + CDiscordUser *pUser = FindUserByChannel(_wtoi64(wszWord.c_str() + 1)); + if (pUser != nullptr) { + ptrW wszNick(getWStringA(pUser->hContact, DB_KEY_NICK)); + if (wszNick != nullptr) + str.Replace(L"<" + wszWord + L">", wszNick); + } + } + } +} diff --git a/protocols/Discord/src/version.h b/protocols/Discord/src/version.h index 138a7eaaec..1a33efa401 100644 --- a/protocols/Discord/src/version.h +++ b/protocols/Discord/src/version.h @@ -1,13 +1,13 @@ -#define __MAJOR_VERSION 0 -#define __MINOR_VERSION 6 -#define __RELEASE_NUM 2 -#define __BUILD_NUM 11 - -#include - -#define __PLUGIN_NAME "Discord protocol" -#define __FILENAME "Discord.dll" -#define __DESCRIPTION "Discord support for Miranda NG." -#define __AUTHOR "George Hazan" -#define __AUTHORWEB "https://miranda-ng.org/p/Discord/" -#define __COPYRIGHT "© 2016-22 Miranda NG team" +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 6 +#define __RELEASE_NUM 2 +#define __BUILD_NUM 11 + +#include + +#define __PLUGIN_NAME "Discord protocol" +#define __FILENAME "Discord.dll" +#define __DESCRIPTION "Discord support for Miranda NG." +#define __AUTHOR "George Hazan" +#define __AUTHORWEB "https://miranda-ng.org/p/Discord/" +#define __COPYRIGHT "© 2016-22 Miranda NG team" diff --git a/protocols/Discord/src/voice.cpp b/protocols/Discord/src/voice.cpp index 6e41bde300..5d1ccf1ea7 100644 --- a/protocols/Discord/src/voice.cpp +++ b/protocols/Discord/src/voice.cpp @@ -1,116 +1,116 @@ -/* -Copyright © 2016-22 Miranda NG team - -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, either version 2 of the License, or -(at your option) any later version. - -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 . -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// call operations (voice & video) - -void CDiscordProto::OnCommandCallCreated(const JSONNode &pRoot) -{ - for (auto &it : pRoot["voice_states"]) { - SnowFlake channelId = ::getId(pRoot["channel_id"]); - auto *pUser = FindUserByChannel(channelId); - if (pUser == nullptr) { - debugLogA("Call from unknown channel %lld, skipping", channelId); - continue; - } - - auto *pCall = new CDiscordVoiceCall(); - pCall->szId = it["session_id"].as_mstring(); - pCall->channelId = channelId; - pCall->startTime = time(0); - arVoiceCalls.insert(pCall); - - char *szMessage = TranslateU("Incoming call"); - DBEVENTINFO dbei = {}; - dbei.szModule = m_szModuleName; - dbei.timestamp = pCall->startTime; - dbei.eventType = EVENT_INCOMING_CALL; - dbei.cbBlob = uint32_t(mir_strlen(szMessage) + 1); - dbei.pBlob = (uint8_t *)szMessage; - dbei.flags = DBEF_UTF; - db_event_add(pUser->hContact, &dbei); - } -} - -void CDiscordProto::OnCommandCallDeleted(const JSONNode &pRoot) -{ - SnowFlake channelId = ::getId(pRoot["channel_id"]); - auto *pUser = FindUserByChannel(channelId); - if (pUser == nullptr) { - debugLogA("Call from unknown channel %lld, skipping", channelId); - return; - } - - int elapsed = 0, currTime = time(0); - for (auto &call : arVoiceCalls.rev_iter()) - if (call->channelId == channelId) { - elapsed = currTime - call->startTime; - arVoiceCalls.removeItem(&call); - break; - } - - if (!elapsed) { - debugLogA("Call from channel %lld isn't registered, skipping", channelId); - return; - } - - CMStringA szMessage(FORMAT, TranslateU("Call ended, %d seconds long"), elapsed); - DBEVENTINFO dbei = {}; - dbei.szModule = m_szModuleName; - dbei.timestamp = currTime; - dbei.eventType = EVENT_CALL_FINISHED; - dbei.cbBlob = uint32_t(szMessage.GetLength() + 1); - dbei.pBlob = (uint8_t *)szMessage.c_str(); - dbei.flags = DBEF_UTF; - db_event_add(pUser->hContact, &dbei); -} - -void CDiscordProto::OnCommandCallUpdated(const JSONNode&) -{ -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Events & services - -INT_PTR __cdecl CDiscordProto::VoiceCaps(WPARAM, LPARAM) -{ - return VOICE_CAPS_VOICE | VOICE_CAPS_CALL_CONTACT; -} - -int __cdecl CDiscordProto::OnVoiceState(WPARAM wParam, LPARAM) -{ - auto *pVoice = (VOICE_CALL *)wParam; - if (mir_strcmp(pVoice->moduleName, m_szModuleName)) - return 0; - - CDiscordVoiceCall *pCall = nullptr; - for (auto &it : arVoiceCalls) - if (it->szId == pVoice->id) { - pCall = it; - break; - } - - if (pCall == nullptr) { - debugLogA("Unknown call: %s, exiting", pVoice->id); - return 0; - } - - debugLogA("Call %s state changed to %d", pVoice->id, pVoice->state); - return 0; -} +/* +Copyright © 2016-22 Miranda NG team + +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, either version 2 of the License, or +(at your option) any later version. + +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 . +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// +// call operations (voice & video) + +void CDiscordProto::OnCommandCallCreated(const JSONNode &pRoot) +{ + for (auto &it : pRoot["voice_states"]) { + SnowFlake channelId = ::getId(pRoot["channel_id"]); + auto *pUser = FindUserByChannel(channelId); + if (pUser == nullptr) { + debugLogA("Call from unknown channel %lld, skipping", channelId); + continue; + } + + auto *pCall = new CDiscordVoiceCall(); + pCall->szId = it["session_id"].as_mstring(); + pCall->channelId = channelId; + pCall->startTime = time(0); + arVoiceCalls.insert(pCall); + + char *szMessage = TranslateU("Incoming call"); + DBEVENTINFO dbei = {}; + dbei.szModule = m_szModuleName; + dbei.timestamp = pCall->startTime; + dbei.eventType = EVENT_INCOMING_CALL; + dbei.cbBlob = uint32_t(mir_strlen(szMessage) + 1); + dbei.pBlob = (uint8_t *)szMessage; + dbei.flags = DBEF_UTF; + db_event_add(pUser->hContact, &dbei); + } +} + +void CDiscordProto::OnCommandCallDeleted(const JSONNode &pRoot) +{ + SnowFlake channelId = ::getId(pRoot["channel_id"]); + auto *pUser = FindUserByChannel(channelId); + if (pUser == nullptr) { + debugLogA("Call from unknown channel %lld, skipping", channelId); + return; + } + + int elapsed = 0, currTime = time(0); + for (auto &call : arVoiceCalls.rev_iter()) + if (call->channelId == channelId) { + elapsed = currTime - call->startTime; + arVoiceCalls.removeItem(&call); + break; + } + + if (!elapsed) { + debugLogA("Call from channel %lld isn't registered, skipping", channelId); + return; + } + + CMStringA szMessage(FORMAT, TranslateU("Call ended, %d seconds long"), elapsed); + DBEVENTINFO dbei = {}; + dbei.szModule = m_szModuleName; + dbei.timestamp = currTime; + dbei.eventType = EVENT_CALL_FINISHED; + dbei.cbBlob = uint32_t(szMessage.GetLength() + 1); + dbei.pBlob = (uint8_t *)szMessage.c_str(); + dbei.flags = DBEF_UTF; + db_event_add(pUser->hContact, &dbei); +} + +void CDiscordProto::OnCommandCallUpdated(const JSONNode&) +{ +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Events & services + +INT_PTR __cdecl CDiscordProto::VoiceCaps(WPARAM, LPARAM) +{ + return VOICE_CAPS_VOICE | VOICE_CAPS_CALL_CONTACT; +} + +int __cdecl CDiscordProto::OnVoiceState(WPARAM wParam, LPARAM) +{ + auto *pVoice = (VOICE_CALL *)wParam; + if (mir_strcmp(pVoice->moduleName, m_szModuleName)) + return 0; + + CDiscordVoiceCall *pCall = nullptr; + for (auto &it : arVoiceCalls) + if (it->szId == pVoice->id) { + pCall = it; + break; + } + + if (pCall == nullptr) { + debugLogA("Unknown call: %s, exiting", pVoice->id); + return 0; + } + + debugLogA("Call %s state changed to %d", pVoice->id, pVoice->state); + return 0; +} diff --git a/protocols/Sametime/src/meanwhile/src/mw_error.h b/protocols/Sametime/src/meanwhile/src/mw_error.h index e53cc0c996..eec101d295 100644 --- a/protocols/Sametime/src/meanwhile/src/mw_error.h +++ b/protocols/Sametime/src/meanwhile/src/mw_error.h @@ -1,206 +1,206 @@ - -/* - Meanwhile - Unofficial Lotus Sametime Community Client Library - Copyright (C) 2004 Christopher (siege) O'Brien - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef _MW_ERROR_H -#define _MW_ERROR_H - - -/** @file mw_error.h - - Common error code constants used by Meanwhile. - - Not all of these error codes (or even many, really) will ever - actually appear from Meanwhile. These are taken directly from the - houri draft, along with the minimal explanation for each. -*/ - - -#include - - -#ifdef __cplusplus -extern "C" { -#endif - - -/// Miranda NG development start -enum mwReturnCodeType { - mwReturnCodeInfo = 0x1000, - mwReturnCodeError = 0x2000 -}; - -struct mwReturnCodeDesc { - guint16 type; /**< @see mwReturnCodeType */ - char *codeString; /**< return code hex as string */ - char *name; /**< return code name */ - char *description; /**< return code description */ -}; - -struct mwReturnCodeDesc *mwGetReturnCodeDesc(guint32 code); -/// Miranda NG development end - -/** reference to a new string appropriate for the given error code.*/ -char* mwError(guint32 code); - - -/* 8.3 Constants */ -/* 8.3.1 Error Codes */ -/* 8.3.1.1 General error/success codes */ - -/** @enum ERR_GENERAL - general error codes */ -enum ERR_GENERAL { - ERR_SUCCESS = 0x00000000, - ERR_FAILURE = 0x80000000, - ERR_REQUEST_DELAY = 0x00000001, - ERR_REQUEST_INVALID = 0x80000001, - ERR_NOT_LOGGED_IN = 0x80000002, - ERR_NOT_AUTHORIZED = 0x80000003, - ERR_ABORT = 0x80000004, - ERR_NO_ELEMENT = 0x80000005, - ERR_NO_USER = 0x80000006, - ERR_BAD_DATA = 0x80000007, - ERR_NOT_IMPLEMENTED = 0x80000008, - ERR_UNKNOWN_ERROR = 0x80000009, /* what is this? */ - ERR_STARVING = 0x8000000a, - ERR_CHANNEL_NO_SUPPORT = 0x8000000b, - ERR_CHANNEL_EXISTS = 0x8000000c, - ERR_SERVICE_NO_SUPPORT = 0x8000000d, - ERR_PROTOCOL_NO_SUPPORT = 0x8000000e, - ERR_PROTOCOL_NO_SUPPORT2 = 0x8000000f, /* duplicate? */ - ERR_VERSION_NO_SUPPORT = 0x80000010, - ERR_USER_SKETCHY = 0x80000011, - ERR_ALREADY_INITIALIZED = 0x80000013, - ERR_NOT_OWNER = 0x80000014, - ERR_TOKEN_INVALID = 0x80000015, - ERR_TOKEN_EXPIRED = 0x80000016, - ERR_TOKEN_IP_MISMATCH = 0x80000017, - ERR_PORT_IN_USE = 0x80000018, - ERR_NETWORK_DEAD = 0x80000019, - ERR_NO_MASTER_CHANNEL = 0x8000001a, - ERR_ALREADY_SUBSCRIBED = 0x8000001b, - ERR_NOT_SUBSCRIBED = 0x8000001c, - ERR_ENCRYPT_NO_SUPPORT = 0x8000001d, - ERR_ENCRYPT_UNINITIALIZED = 0x8000001e, - ERR_ENCRYPT_UNACCEPTABLE = 0x8000001f, - ERR_ENCRYPT_INVALID = 0x80000020, - ERR_NO_COMMON_ENCRYPT = 0x80000021, - ERR_CHANNEL_DESTROYED = 0x80000022, - /// Miranda NG development start - //ERR_CHANNEL_REDIRECTED = 0x80000023 - ERR_CHANNEL_REDIRECTED = 0x00000023, + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_ERROR_H +#define _MW_ERROR_H + + +/** @file mw_error.h + + Common error code constants used by Meanwhile. + + Not all of these error codes (or even many, really) will ever + actually appear from Meanwhile. These are taken directly from the + houri draft, along with the minimal explanation for each. +*/ + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/// Miranda NG development start +enum mwReturnCodeType { + mwReturnCodeInfo = 0x1000, + mwReturnCodeError = 0x2000 +}; + +struct mwReturnCodeDesc { + guint16 type; /**< @see mwReturnCodeType */ + char *codeString; /**< return code hex as string */ + char *name; /**< return code name */ + char *description; /**< return code description */ +}; + +struct mwReturnCodeDesc *mwGetReturnCodeDesc(guint32 code); +/// Miranda NG development end + +/** reference to a new string appropriate for the given error code.*/ +char* mwError(guint32 code); + + +/* 8.3 Constants */ +/* 8.3.1 Error Codes */ +/* 8.3.1.1 General error/success codes */ + +/** @enum ERR_GENERAL + general error codes */ +enum ERR_GENERAL { + ERR_SUCCESS = 0x00000000, + ERR_FAILURE = 0x80000000, + ERR_REQUEST_DELAY = 0x00000001, + ERR_REQUEST_INVALID = 0x80000001, + ERR_NOT_LOGGED_IN = 0x80000002, + ERR_NOT_AUTHORIZED = 0x80000003, + ERR_ABORT = 0x80000004, + ERR_NO_ELEMENT = 0x80000005, + ERR_NO_USER = 0x80000006, + ERR_BAD_DATA = 0x80000007, + ERR_NOT_IMPLEMENTED = 0x80000008, + ERR_UNKNOWN_ERROR = 0x80000009, /* what is this? */ + ERR_STARVING = 0x8000000a, + ERR_CHANNEL_NO_SUPPORT = 0x8000000b, + ERR_CHANNEL_EXISTS = 0x8000000c, + ERR_SERVICE_NO_SUPPORT = 0x8000000d, + ERR_PROTOCOL_NO_SUPPORT = 0x8000000e, + ERR_PROTOCOL_NO_SUPPORT2 = 0x8000000f, /* duplicate? */ + ERR_VERSION_NO_SUPPORT = 0x80000010, + ERR_USER_SKETCHY = 0x80000011, + ERR_ALREADY_INITIALIZED = 0x80000013, + ERR_NOT_OWNER = 0x80000014, + ERR_TOKEN_INVALID = 0x80000015, + ERR_TOKEN_EXPIRED = 0x80000016, + ERR_TOKEN_IP_MISMATCH = 0x80000017, + ERR_PORT_IN_USE = 0x80000018, + ERR_NETWORK_DEAD = 0x80000019, + ERR_NO_MASTER_CHANNEL = 0x8000001a, + ERR_ALREADY_SUBSCRIBED = 0x8000001b, + ERR_NOT_SUBSCRIBED = 0x8000001c, + ERR_ENCRYPT_NO_SUPPORT = 0x8000001d, + ERR_ENCRYPT_UNINITIALIZED = 0x8000001e, + ERR_ENCRYPT_UNACCEPTABLE = 0x8000001f, + ERR_ENCRYPT_INVALID = 0x80000020, + ERR_NO_COMMON_ENCRYPT = 0x80000021, + ERR_CHANNEL_DESTROYED = 0x80000022, + /// Miranda NG development start + //ERR_CHANNEL_REDIRECTED = 0x80000023 + ERR_CHANNEL_REDIRECTED = 0x00000023, ERR_INCORRECT_ENTRY = 0x80000239 - /// Miranda NG development end -}; - - -/* 8.3.1.2 Connection/disconnection errors */ - -#define VERSION_MISMATCH 0x80000200 -#define INSUF_BUFFER 0x80000201 -#define NOT_IN_USE 0x80000202 -#define INSUF_SOCKET 0x80000203 -#define HARDWARE_ERROR 0x80000204 -#define NETWORK_DOWN 0x80000205 -#define HOST_DOWN 0x80000206 -#define HOST_UNREACHABLE 0x80000207 -#define TCPIP_ERROR 0x80000208 -#define FAT_MESSAGE 0x80000209 -#define PROXY_ERROR 0x8000020A -#define SERVER_FULL 0x8000020B -#define SERVER_NORESPOND 0x8000020C -#define CANT_CONNECT 0x8000020D -#define USER_REMOVED 0x8000020E -#define PROTOCOL_ERROR 0x8000020F -#define USER_RESTRICTED 0x80000210 -#define INCORRECT_LOGIN 0x80000211 -#define ENCRYPT_MISMATCH 0x80000212 -#define USER_UNREGISTERED 0x80000213 -#define VERIFICATION_DOWN 0x80000214 -#define USER_TOO_IDLE 0x80000216 -#define GUEST_IN_USE 0x80000217 -#define USER_EXISTS 0x80000218 -#define USER_RE_LOGIN 0x80000219 -#define BAD_NAME 0x8000021A -#define REG_MODE_NS 0x8000021B -#define WRONG_USER_PRIV 0x8000021C -#define NEED_EMAIL 0x8000021D -#define DNS_ERROR 0x8000021E -#define DNS_FATAL_ERROR 0x8000021F -#define DNS_NOT_FOUND 0x80000220 -#define CONNECTION_BROKEN 0x80000221 -#define CONNECTION_ABORTED 0x80000222 -#define CONNECTION_REFUSED 0x80000223 -#define CONNECTION_RESET 0x80000224 -#define CONNECTION_TIMED 0x80000225 -#define CONNECTION_CLOSED 0x80000226 -#define MULTI_SERVER_LOGIN 0x80000227 -#define MULTI_SERVER_LOGIN2 0x80000228 -#define MULTI_LOGIN_COMP 0x80000229 -#define MUTLI_LOGIN_ALREADY 0x8000022A -#define SERVER_BROKEN 0x8000022B -#define SERVER_PATH_OLD 0x8000022C -#define APPLET_LOGOUT 0x8000022D - - -/* 8.3.1.3 Client error codes */ - -/** @enum ERR_CLIENT - Client error codes */ -enum ERR_CLIENT { - ERR_CLIENT_USER_GONE = 0x80002000, /* user isn't here */ - ERR_CLIENT_USER_DND = 0x80002001, /* user is DND */ - ERR_CLIENT_USER_ELSEWHERE = 0x80002002, /* already logged in elsewhere */ -}; - - -/* 8.3.1.4 IM error codes */ - -/** @enum ERR_IM - IM error codes */ -enum ERR_IM { - ERR_IM_COULDNT_REGISTER = 0x80002003, - ERR_IM_ALREADY_REGISTERED = 0x80002004, - - /** apparently, this is used to mean that the requested feature (per - the channel create addtl data) is not supported by the client on - the other end of the IM channel */ - ERR_IM_NOT_REGISTERED = 0x80002005, -}; - - -/// Miranda NG development start -/* 8.3.1.5 Resolve error codes */ - -/** @enum ERR_RESOLVE -Resolve error codes */ -enum ERR_RESOLVE { - ERR_RESOLVE_NOTCOMPLETED = 0x00010000, - ERR_RESOLVE_NAMENOTUNIQUE = 0x80020000, + /// Miranda NG development end +}; + + +/* 8.3.1.2 Connection/disconnection errors */ + +#define VERSION_MISMATCH 0x80000200 +#define INSUF_BUFFER 0x80000201 +#define NOT_IN_USE 0x80000202 +#define INSUF_SOCKET 0x80000203 +#define HARDWARE_ERROR 0x80000204 +#define NETWORK_DOWN 0x80000205 +#define HOST_DOWN 0x80000206 +#define HOST_UNREACHABLE 0x80000207 +#define TCPIP_ERROR 0x80000208 +#define FAT_MESSAGE 0x80000209 +#define PROXY_ERROR 0x8000020A +#define SERVER_FULL 0x8000020B +#define SERVER_NORESPOND 0x8000020C +#define CANT_CONNECT 0x8000020D +#define USER_REMOVED 0x8000020E +#define PROTOCOL_ERROR 0x8000020F +#define USER_RESTRICTED 0x80000210 +#define INCORRECT_LOGIN 0x80000211 +#define ENCRYPT_MISMATCH 0x80000212 +#define USER_UNREGISTERED 0x80000213 +#define VERIFICATION_DOWN 0x80000214 +#define USER_TOO_IDLE 0x80000216 +#define GUEST_IN_USE 0x80000217 +#define USER_EXISTS 0x80000218 +#define USER_RE_LOGIN 0x80000219 +#define BAD_NAME 0x8000021A +#define REG_MODE_NS 0x8000021B +#define WRONG_USER_PRIV 0x8000021C +#define NEED_EMAIL 0x8000021D +#define DNS_ERROR 0x8000021E +#define DNS_FATAL_ERROR 0x8000021F +#define DNS_NOT_FOUND 0x80000220 +#define CONNECTION_BROKEN 0x80000221 +#define CONNECTION_ABORTED 0x80000222 +#define CONNECTION_REFUSED 0x80000223 +#define CONNECTION_RESET 0x80000224 +#define CONNECTION_TIMED 0x80000225 +#define CONNECTION_CLOSED 0x80000226 +#define MULTI_SERVER_LOGIN 0x80000227 +#define MULTI_SERVER_LOGIN2 0x80000228 +#define MULTI_LOGIN_COMP 0x80000229 +#define MUTLI_LOGIN_ALREADY 0x8000022A +#define SERVER_BROKEN 0x8000022B +#define SERVER_PATH_OLD 0x8000022C +#define APPLET_LOGOUT 0x8000022D + + +/* 8.3.1.3 Client error codes */ + +/** @enum ERR_CLIENT + Client error codes */ +enum ERR_CLIENT { + ERR_CLIENT_USER_GONE = 0x80002000, /* user isn't here */ + ERR_CLIENT_USER_DND = 0x80002001, /* user is DND */ + ERR_CLIENT_USER_ELSEWHERE = 0x80002002, /* already logged in elsewhere */ +}; + + +/* 8.3.1.4 IM error codes */ + +/** @enum ERR_IM + IM error codes */ +enum ERR_IM { + ERR_IM_COULDNT_REGISTER = 0x80002003, + ERR_IM_ALREADY_REGISTERED = 0x80002004, + + /** apparently, this is used to mean that the requested feature (per + the channel create addtl data) is not supported by the client on + the other end of the IM channel */ + ERR_IM_NOT_REGISTERED = 0x80002005, +}; + + +/// Miranda NG development start +/* 8.3.1.5 Resolve error codes */ + +/** @enum ERR_RESOLVE +Resolve error codes */ +enum ERR_RESOLVE { + ERR_RESOLVE_NOTCOMPLETED = 0x00010000, + ERR_RESOLVE_NAMENOTUNIQUE = 0x80020000, ERR_RESOLVE_NAMENOTRESOLVABLE = 0x80030000 -}; -/// Miranda NG development end - -#ifdef __cplusplus -} -#endif - - -#endif /* _MW_ERROR_H */ +}; +/// Miranda NG development end + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_ERROR_H */ diff --git a/protocols/Steam/src/api/enums.h b/protocols/Steam/src/api/enums.h index 087b99c5fa..2cf767e37f 100644 --- a/protocols/Steam/src/api/enums.h +++ b/protocols/Steam/src/api/enums.h @@ -1,88 +1,88 @@ -#ifndef _STEAM_ENUMS_H_ -#define _STEAM_ENUMS_H_ - -enum VisibilityState -{ - Private = 1, - FriendsOnly = 2, - FriendsOfFriends = 3, - UsersOnly = 4, - Public = 5, -}; - -enum PersonaState -{ - Offline = 0, - Online = 1, - Busy = 2, - Away = 3, - Snooze = 4, - LookingToTrade = 5, - LookingToPlay = 6, - Invisible = 7, -}; - -enum class PersonaStateFlag : int -{ - None = 0, - HasRichPresence = 1, - InJoinableGame = 2, - ClientTypeWeb = 256, - ClientTypeMobile = 512, - ClientTypeBigPicture = 1024, - ClientTypeVR = 2048, -}; - -inline PersonaStateFlag operator &(PersonaStateFlag lhs, PersonaStateFlag rhs) -{ - return static_cast ( - static_cast::type>(lhs) & - static_cast::type>(rhs)); -} - -enum class PersonaStatusFlag : int -{ - Status = 1, - PlayerName = 2, - QueryPort = 4, - SourceID = 8, - Presence = 16, - Metadata = 32, - LastSeen = 64, - ClanInfo = 128, - GameExtraInfo = 256, - GameDataBlob = 512, - ClanTag = 1024, - Facebook = 2048, - Unknown = 4096, -}; - -inline PersonaStatusFlag operator &(PersonaStatusFlag lhs, PersonaStatusFlag rhs) -{ - return static_cast ( - static_cast::type>(lhs) & - static_cast::type>(rhs)); -} - -enum class PersonaRelationshipAction : int -{ - // friend removed from contact list - Remove = 0, - // friend added you to ignore list - Ignore = 1, - // friend requested auth - AuthRequest = 2, - // friend added you to contact list - AddToList = 3, - // friend got (or approved?) your auth request - AuthRequested = 4, -}; - -template -bool contains_flag(T x, T y) { - return (static_cast::type>(x) - & static_cast::type>(y)) - == static_cast::type>(y); -} - -#endif //_STEAM_ENUMS_H_ +#ifndef _STEAM_ENUMS_H_ +#define _STEAM_ENUMS_H_ + +enum VisibilityState +{ + Private = 1, + FriendsOnly = 2, + FriendsOfFriends = 3, + UsersOnly = 4, + Public = 5, +}; + +enum PersonaState +{ + Offline = 0, + Online = 1, + Busy = 2, + Away = 3, + Snooze = 4, + LookingToTrade = 5, + LookingToPlay = 6, + Invisible = 7, +}; + +enum class PersonaStateFlag : int +{ + None = 0, + HasRichPresence = 1, + InJoinableGame = 2, + ClientTypeWeb = 256, + ClientTypeMobile = 512, + ClientTypeBigPicture = 1024, + ClientTypeVR = 2048, +}; + +inline PersonaStateFlag operator &(PersonaStateFlag lhs, PersonaStateFlag rhs) +{ + return static_cast ( + static_cast::type>(lhs) & + static_cast::type>(rhs)); +} + +enum class PersonaStatusFlag : int +{ + Status = 1, + PlayerName = 2, + QueryPort = 4, + SourceID = 8, + Presence = 16, + Metadata = 32, + LastSeen = 64, + ClanInfo = 128, + GameExtraInfo = 256, + GameDataBlob = 512, + ClanTag = 1024, + Facebook = 2048, + Unknown = 4096, +}; + +inline PersonaStatusFlag operator &(PersonaStatusFlag lhs, PersonaStatusFlag rhs) +{ + return static_cast ( + static_cast::type>(lhs) & + static_cast::type>(rhs)); +} + +enum class PersonaRelationshipAction : int +{ + // friend removed from contact list + Remove = 0, + // friend added you to ignore list + Ignore = 1, + // friend requested auth + AuthRequest = 2, + // friend added you to contact list + AddToList = 3, + // friend got (or approved?) your auth request + AuthRequested = 4, +}; + +template +bool contains_flag(T x, T y) { + return (static_cast::type>(x) + & static_cast::type>(y)) + == static_cast::type>(y); +} + +#endif //_STEAM_ENUMS_H_ diff --git a/protocols/Steam/src/steam_proto.cpp b/protocols/Steam/src/steam_proto.cpp index a6bac4aafb..f185171edd 100644 --- a/protocols/Steam/src/steam_proto.cpp +++ b/protocols/Steam/src/steam_proto.cpp @@ -1,354 +1,354 @@ -#include "stdafx.h" - -CSteamProto::CSteamProto(const char *protoName, const wchar_t *userName) : - PROTO(protoName, userName), - m_requestQueue(1), hAuthProcess(1), hMessageProcess(1) -{ - CreateProtoService(PS_CREATEACCMGRUI, &CSteamProto::OnAccountManagerInit); - - m_hRequestsQueueEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - - // default group - m_defaultGroup = getWStringA("DefaultGroup"); - if (m_defaultGroup == nullptr) - m_defaultGroup = mir_wstrdup(L"Steam"); - - // icons - wchar_t filePath[MAX_PATH]; - GetModuleFileName(g_plugin.getInst(), filePath, MAX_PATH); - - wchar_t sectionName[100]; - mir_snwprintf(sectionName, L"%s/%s", LPGENW("Protocols"), _A2W(MODULE)); - - char settingName[100]; - mir_snprintf(settingName, "%s_%s", MODULE, "main"); - - SKINICONDESC sid = {}; - sid.flags = SIDF_ALL_UNICODE; - sid.defaultFile.w = filePath; - sid.pszName = settingName; - sid.section.w = sectionName; - sid.description.w = LPGENW("Protocol icon"); - sid.iDefaultIndex = -IDI_STEAM; - g_plugin.addIcon(&sid); - - mir_snprintf(settingName, "%s_%s", MODULE, "gaming"); - sid.description.w = LPGENW("Gaming icon"); - sid.iDefaultIndex = -IDI_GAMING; - g_plugin.addIcon(&sid); - - // temporary DB settings - db_set_resident(m_szModuleName, "XStatusId"); - db_set_resident(m_szModuleName, "XStatusName"); - db_set_resident(m_szModuleName, "XStatusMsg"); - db_set_resident(m_szModuleName, "IdleTS"); - db_set_resident(m_szModuleName, "GameID"); - db_set_resident(m_szModuleName, "ServerIP"); - db_set_resident(m_szModuleName, "ServerID"); - - SetAllContactStatuses(ID_STATUS_OFFLINE); - - // avatar API - CreateProtoService(PS_GETAVATARINFO, &CSteamProto::GetAvatarInfo); - CreateProtoService(PS_GETAVATARCAPS, &CSteamProto::GetAvatarCaps); - CreateProtoService(PS_GETMYAVATAR, &CSteamProto::GetMyAvatar); - - // custom status API - CreateProtoService(PS_GETCUSTOMSTATUSEX, &CSteamProto::OnGetXStatusEx); - CreateProtoService(PS_GETCUSTOMSTATUSICON, &CSteamProto::OnGetXStatusIcon); - CreateProtoService(PS_GETADVANCEDSTATUSICON, &CSteamProto::OnRequestAdvStatusIconIdx); - - // menus - CreateProtoService(PS_MENU_REQAUTH, &CSteamProto::AuthRequestCommand); - CreateProtoService(PS_MENU_REVOKEAUTH, &CSteamProto::AuthRevokeCommand); - - // custom db events API - CreateProtoService(STEAM_DB_GETEVENTTEXT_CHATSTATES, &CSteamProto::OnGetEventTextChatStates); - - // hooks - HookProtoEvent(ME_OPT_INITIALISE, &CSteamProto::OnOptionsInit); - - // netlib support - NETLIBUSER nlu = {}; - nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; - nlu.szDescriptiveName.w = m_tszUserName; - nlu.szSettingsModule = m_szModuleName; - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - debugLogA(__FUNCTION__":Setting protocol / module name to '%s'", m_szModuleName); - - if (uint32_t iGlobalValue = getDword(DB_KEY_LASTMSGTS)) { - for (auto &cc : AccContacts()) - setDword(cc, DB_KEY_LASTMSGTS, iGlobalValue); - delSetting(DB_KEY_LASTMSGTS); - } -} - -CSteamProto::~CSteamProto() -{ - if (m_hRequestsQueueEvent) { - CloseHandle(m_hRequestsQueueEvent); - m_hRequestsQueueEvent = nullptr; - } -} - -MCONTACT CSteamProto::AddToList(int, PROTOSEARCHRESULT *psr) -{ - MCONTACT hContact = AddContact(T2Utf(psr->id.w), psr->nick.w, true); - - if (psr->cbSize == sizeof(STEAM_SEARCH_RESULT)) { - STEAM_SEARCH_RESULT *ssr = (STEAM_SEARCH_RESULT *)psr; - UpdateContactDetails(hContact, *ssr->data); - } - - return hContact; -} - -MCONTACT CSteamProto::AddToListByEvent(int, int, MEVENT hDbEvent) -{ - DB::EventInfo dbei; - dbei.cbBlob = -1; - if (db_event_get(hDbEvent, &dbei)) - return 0; - if (mir_strcmp(dbei.szModule, m_szModuleName)) - return 0; - if (dbei.eventType != EVENTTYPE_AUTHREQUEST) - return 0; - - DB::AUTH_BLOB blob(dbei.pBlob); - return AddContact(blob.get_email(), Utf2T(blob.get_nick())); -} - -int CSteamProto::Authorize(MEVENT hDbEvent) -{ - if (IsOnline() && hDbEvent) { - MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); - if (hContact == INVALID_CONTACT_ID) - return 1; - - ptrA token(getStringA("TokenSecret")); - ptrA sessionId(getStringA("SessionID")); - ptrA steamId(getStringA("SteamID")); - char *who = getStringA(hContact, "SteamID"); - - PushRequest( - new ApprovePendingRequest(token, sessionId, steamId, who), - &CSteamProto::OnPendingApproved, - who); - - return 0; - } - - return 1; -} - -int CSteamProto::AuthRecv(MCONTACT hContact, PROTORECVEVENT *pre) -{ - // remember to not create this event again, unless authorization status changes again - setByte(hContact, "AuthAsked", 1); - return Proto_AuthRecv(m_szModuleName, pre); -} - -int CSteamProto::AuthDeny(MEVENT hDbEvent, const wchar_t*) -{ - if (IsOnline() && hDbEvent) { - MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); - if (hContact == INVALID_CONTACT_ID) - return 1; - - ptrA token(getStringA("TokenSecret")); - ptrA sessionId(getStringA("SessionID")); - ptrA steamId(getStringA("SteamID")); - char *who = getStringA(hContact, "SteamID"); - - PushRequest( - new IgnorePendingRequest(token, sessionId, steamId, who), - &CSteamProto::OnPendingIgnoreded, - who); - - return 0; - } - - return 1; -} - -int CSteamProto::AuthRequest(MCONTACT hContact, const wchar_t*) -{ - if (IsOnline() && hContact) { - UINT hAuth = InterlockedIncrement(&hAuthProcess); - - SendAuthParam *param = (SendAuthParam*)mir_calloc(sizeof(SendAuthParam)); - param->hContact = hContact; - param->hAuth = (HANDLE)hAuth; - - ptrA token(getStringA("TokenSecret")); - ptrA sessionId(getStringA("SessionID")); - ptrA steamId(getStringA("SteamID")); - ptrA who(getStringA(hContact, "SteamID")); - - PushRequest( - new AddFriendRequest(token, sessionId, steamId, who), - &CSteamProto::OnFriendAdded, - param); - - return hAuth; - } - - return 1; -} - -INT_PTR CSteamProto::GetCaps(int type, MCONTACT) -{ - switch (type) { - case PFLAGNUM_1: - return PF1_IM | PF1_BASICSEARCH | PF1_SEARCHBYNAME | PF1_AUTHREQ | PF1_SERVERCLIST | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; - case PFLAGNUM_2: - return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE; - case PFLAGNUM_4: - return PF4_AVATARS | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_FORCEAUTH | PF4_SUPPORTIDLE | PF4_SUPPORTTYPING;// | PF4_IMSENDOFFLINE; - case PFLAGNUM_5: - return PF2_HEAVYDND | PF2_FREECHAT; - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)TranslateT("SteamID"); - default: - return 0; - } -} - -HANDLE CSteamProto::SearchBasic(const wchar_t* id) -{ - if (!this->IsOnline()) - return nullptr; - - ptrA steamId(mir_u2a(id)); - PushRequest(new GetUserSummariesRequest(this, steamId), &CSteamProto::OnSearchResults, (HANDLE)STEAM_SEARCH_BYID); - - return (HANDLE)STEAM_SEARCH_BYID; -} - -HANDLE CSteamProto::SearchByName(const wchar_t *nick, const wchar_t *firstName, const wchar_t *lastName) -{ - if (!IsOnline()) - return nullptr; - - // Combine all fields to single text - wchar_t keywordsT[200]; - mir_snwprintf(keywordsT, L"%s %s %s", nick, firstName, lastName); - - ptrA token(getStringA("TokenSecret")); - ptrA keywords(mir_utf8encodeW(rtrimw(keywordsT))); - - PushRequest( - new SearchRequest(token, keywords), - &CSteamProto::OnSearchByNameStarted, - (HANDLE)STEAM_SEARCH_BYNAME); - - return (HANDLE)STEAM_SEARCH_BYNAME; -} - -int CSteamProto::SendMsg(MCONTACT hContact, int, const char *message) -{ - if (!IsOnline()) { - ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, nullptr, (LPARAM)TranslateT("You cannot send messages when you are offline.")); - return 0; - } - - return OnSendMessage(hContact, message); -} - -int CSteamProto::SetStatus(int new_status) -{ - // Routing statuses not supported by Steam - switch (new_status) { - case ID_STATUS_OFFLINE: - case ID_STATUS_AWAY: - case ID_STATUS_NA: - case ID_STATUS_INVISIBLE: - break; - - case ID_STATUS_DND: - case ID_STATUS_OCCUPIED: - new_status = ID_STATUS_NA; - break; - - default: - new_status = ID_STATUS_ONLINE; - break; - } - - { - mir_cslock lock(m_setStatusLock); - if (new_status == m_iDesiredStatus) - return 0; - } - - debugLogA(__FUNCTION__ ": changing status from %i to %i", m_iStatus, new_status); - - int old_status = m_iStatus; - m_iDesiredStatus = new_status; - - if (new_status == ID_STATUS_OFFLINE) { - // Reset relogin flag - isLoginAgain = false; - - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - if (!Miranda_IsTerminated()) - SetAllContactStatuses(ID_STATUS_OFFLINE); - - Logout(); - } - else if (m_hRequestQueueThread == nullptr && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - m_isTerminated = false; - ForkThread(&CSteamProto::RequestQueueThread); - - Login(); - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); - } - else if (IsOnline()) { - m_iStatus = new_status; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); - } - return 0; -} - -void CSteamProto::GetAwayMsgThread(void *arg) -{ - // Maybe not needed, but better to be sure that this won't happen faster than core handling return value of GetAwayMsg() - Sleep(50); - - MCONTACT hContact = (UINT_PTR)arg; - CMStringW message(db_get_wsm(hContact, "CList", "StatusMsg")); - - // if contact has no status message, get xstatus message - if (message.IsEmpty()) { - ptrW xStatusName(getWStringA(hContact, "XStatusName")); - ptrW xStatusMsg(getWStringA(hContact, "XStatusMsg")); - - if (xStatusName) - message.AppendFormat(L"%s: %s", xStatusName.get(), xStatusMsg.get()); - else - message.Append(xStatusMsg); - } - - ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)message.c_str()); -} - -HANDLE CSteamProto::GetAwayMsg(MCONTACT hContact) -{ - ForkThread(&CSteamProto::GetAwayMsgThread, (void*)hContact); - return (HANDLE)1; -} - -void CSteamProto::OnContactDeleted(MCONTACT hContact) -{ - // remove only authorized contacts - if (!getByte(hContact, "Auth", 0)) { - ptrA token(getStringA("TokenSecret")); - ptrA sessionId(getStringA("SessionID")); - ptrA steamId(getStringA("SteamID")); - char *who = getStringA(hContact, "SteamID"); - PushRequest(new RemoveFriendRequest(token, sessionId, steamId, who), &CSteamProto::OnFriendRemoved, (void*)who); - } -} +#include "stdafx.h" + +CSteamProto::CSteamProto(const char *protoName, const wchar_t *userName) : + PROTO(protoName, userName), + m_requestQueue(1), hAuthProcess(1), hMessageProcess(1) +{ + CreateProtoService(PS_CREATEACCMGRUI, &CSteamProto::OnAccountManagerInit); + + m_hRequestsQueueEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + // default group + m_defaultGroup = getWStringA("DefaultGroup"); + if (m_defaultGroup == nullptr) + m_defaultGroup = mir_wstrdup(L"Steam"); + + // icons + wchar_t filePath[MAX_PATH]; + GetModuleFileName(g_plugin.getInst(), filePath, MAX_PATH); + + wchar_t sectionName[100]; + mir_snwprintf(sectionName, L"%s/%s", LPGENW("Protocols"), _A2W(MODULE)); + + char settingName[100]; + mir_snprintf(settingName, "%s_%s", MODULE, "main"); + + SKINICONDESC sid = {}; + sid.flags = SIDF_ALL_UNICODE; + sid.defaultFile.w = filePath; + sid.pszName = settingName; + sid.section.w = sectionName; + sid.description.w = LPGENW("Protocol icon"); + sid.iDefaultIndex = -IDI_STEAM; + g_plugin.addIcon(&sid); + + mir_snprintf(settingName, "%s_%s", MODULE, "gaming"); + sid.description.w = LPGENW("Gaming icon"); + sid.iDefaultIndex = -IDI_GAMING; + g_plugin.addIcon(&sid); + + // temporary DB settings + db_set_resident(m_szModuleName, "XStatusId"); + db_set_resident(m_szModuleName, "XStatusName"); + db_set_resident(m_szModuleName, "XStatusMsg"); + db_set_resident(m_szModuleName, "IdleTS"); + db_set_resident(m_szModuleName, "GameID"); + db_set_resident(m_szModuleName, "ServerIP"); + db_set_resident(m_szModuleName, "ServerID"); + + SetAllContactStatuses(ID_STATUS_OFFLINE); + + // avatar API + CreateProtoService(PS_GETAVATARINFO, &CSteamProto::GetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &CSteamProto::GetAvatarCaps); + CreateProtoService(PS_GETMYAVATAR, &CSteamProto::GetMyAvatar); + + // custom status API + CreateProtoService(PS_GETCUSTOMSTATUSEX, &CSteamProto::OnGetXStatusEx); + CreateProtoService(PS_GETCUSTOMSTATUSICON, &CSteamProto::OnGetXStatusIcon); + CreateProtoService(PS_GETADVANCEDSTATUSICON, &CSteamProto::OnRequestAdvStatusIconIdx); + + // menus + CreateProtoService(PS_MENU_REQAUTH, &CSteamProto::AuthRequestCommand); + CreateProtoService(PS_MENU_REVOKEAUTH, &CSteamProto::AuthRevokeCommand); + + // custom db events API + CreateProtoService(STEAM_DB_GETEVENTTEXT_CHATSTATES, &CSteamProto::OnGetEventTextChatStates); + + // hooks + HookProtoEvent(ME_OPT_INITIALISE, &CSteamProto::OnOptionsInit); + + // netlib support + NETLIBUSER nlu = {}; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szDescriptiveName.w = m_tszUserName; + nlu.szSettingsModule = m_szModuleName; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + debugLogA(__FUNCTION__":Setting protocol / module name to '%s'", m_szModuleName); + + if (uint32_t iGlobalValue = getDword(DB_KEY_LASTMSGTS)) { + for (auto &cc : AccContacts()) + setDword(cc, DB_KEY_LASTMSGTS, iGlobalValue); + delSetting(DB_KEY_LASTMSGTS); + } +} + +CSteamProto::~CSteamProto() +{ + if (m_hRequestsQueueEvent) { + CloseHandle(m_hRequestsQueueEvent); + m_hRequestsQueueEvent = nullptr; + } +} + +MCONTACT CSteamProto::AddToList(int, PROTOSEARCHRESULT *psr) +{ + MCONTACT hContact = AddContact(T2Utf(psr->id.w), psr->nick.w, true); + + if (psr->cbSize == sizeof(STEAM_SEARCH_RESULT)) { + STEAM_SEARCH_RESULT *ssr = (STEAM_SEARCH_RESULT *)psr; + UpdateContactDetails(hContact, *ssr->data); + } + + return hContact; +} + +MCONTACT CSteamProto::AddToListByEvent(int, int, MEVENT hDbEvent) +{ + DB::EventInfo dbei; + dbei.cbBlob = -1; + if (db_event_get(hDbEvent, &dbei)) + return 0; + if (mir_strcmp(dbei.szModule, m_szModuleName)) + return 0; + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) + return 0; + + DB::AUTH_BLOB blob(dbei.pBlob); + return AddContact(blob.get_email(), Utf2T(blob.get_nick())); +} + +int CSteamProto::Authorize(MEVENT hDbEvent) +{ + if (IsOnline() && hDbEvent) { + MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); + if (hContact == INVALID_CONTACT_ID) + return 1; + + ptrA token(getStringA("TokenSecret")); + ptrA sessionId(getStringA("SessionID")); + ptrA steamId(getStringA("SteamID")); + char *who = getStringA(hContact, "SteamID"); + + PushRequest( + new ApprovePendingRequest(token, sessionId, steamId, who), + &CSteamProto::OnPendingApproved, + who); + + return 0; + } + + return 1; +} + +int CSteamProto::AuthRecv(MCONTACT hContact, PROTORECVEVENT *pre) +{ + // remember to not create this event again, unless authorization status changes again + setByte(hContact, "AuthAsked", 1); + return Proto_AuthRecv(m_szModuleName, pre); +} + +int CSteamProto::AuthDeny(MEVENT hDbEvent, const wchar_t*) +{ + if (IsOnline() && hDbEvent) { + MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); + if (hContact == INVALID_CONTACT_ID) + return 1; + + ptrA token(getStringA("TokenSecret")); + ptrA sessionId(getStringA("SessionID")); + ptrA steamId(getStringA("SteamID")); + char *who = getStringA(hContact, "SteamID"); + + PushRequest( + new IgnorePendingRequest(token, sessionId, steamId, who), + &CSteamProto::OnPendingIgnoreded, + who); + + return 0; + } + + return 1; +} + +int CSteamProto::AuthRequest(MCONTACT hContact, const wchar_t*) +{ + if (IsOnline() && hContact) { + UINT hAuth = InterlockedIncrement(&hAuthProcess); + + SendAuthParam *param = (SendAuthParam*)mir_calloc(sizeof(SendAuthParam)); + param->hContact = hContact; + param->hAuth = (HANDLE)hAuth; + + ptrA token(getStringA("TokenSecret")); + ptrA sessionId(getStringA("SessionID")); + ptrA steamId(getStringA("SteamID")); + ptrA who(getStringA(hContact, "SteamID")); + + PushRequest( + new AddFriendRequest(token, sessionId, steamId, who), + &CSteamProto::OnFriendAdded, + param); + + return hAuth; + } + + return 1; +} + +INT_PTR CSteamProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_BASICSEARCH | PF1_SEARCHBYNAME | PF1_AUTHREQ | PF1_SERVERCLIST | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; + case PFLAGNUM_2: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE; + case PFLAGNUM_4: + return PF4_AVATARS | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_FORCEAUTH | PF4_SUPPORTIDLE | PF4_SUPPORTTYPING;// | PF4_IMSENDOFFLINE; + case PFLAGNUM_5: + return PF2_HEAVYDND | PF2_FREECHAT; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)TranslateT("SteamID"); + default: + return 0; + } +} + +HANDLE CSteamProto::SearchBasic(const wchar_t* id) +{ + if (!this->IsOnline()) + return nullptr; + + ptrA steamId(mir_u2a(id)); + PushRequest(new GetUserSummariesRequest(this, steamId), &CSteamProto::OnSearchResults, (HANDLE)STEAM_SEARCH_BYID); + + return (HANDLE)STEAM_SEARCH_BYID; +} + +HANDLE CSteamProto::SearchByName(const wchar_t *nick, const wchar_t *firstName, const wchar_t *lastName) +{ + if (!IsOnline()) + return nullptr; + + // Combine all fields to single text + wchar_t keywordsT[200]; + mir_snwprintf(keywordsT, L"%s %s %s", nick, firstName, lastName); + + ptrA token(getStringA("TokenSecret")); + ptrA keywords(mir_utf8encodeW(rtrimw(keywordsT))); + + PushRequest( + new SearchRequest(token, keywords), + &CSteamProto::OnSearchByNameStarted, + (HANDLE)STEAM_SEARCH_BYNAME); + + return (HANDLE)STEAM_SEARCH_BYNAME; +} + +int CSteamProto::SendMsg(MCONTACT hContact, int, const char *message) +{ + if (!IsOnline()) { + ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, nullptr, (LPARAM)TranslateT("You cannot send messages when you are offline.")); + return 0; + } + + return OnSendMessage(hContact, message); +} + +int CSteamProto::SetStatus(int new_status) +{ + // Routing statuses not supported by Steam + switch (new_status) { + case ID_STATUS_OFFLINE: + case ID_STATUS_AWAY: + case ID_STATUS_NA: + case ID_STATUS_INVISIBLE: + break; + + case ID_STATUS_DND: + case ID_STATUS_OCCUPIED: + new_status = ID_STATUS_NA; + break; + + default: + new_status = ID_STATUS_ONLINE; + break; + } + + { + mir_cslock lock(m_setStatusLock); + if (new_status == m_iDesiredStatus) + return 0; + } + + debugLogA(__FUNCTION__ ": changing status from %i to %i", m_iStatus, new_status); + + int old_status = m_iStatus; + m_iDesiredStatus = new_status; + + if (new_status == ID_STATUS_OFFLINE) { + // Reset relogin flag + isLoginAgain = false; + + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + if (!Miranda_IsTerminated()) + SetAllContactStatuses(ID_STATUS_OFFLINE); + + Logout(); + } + else if (m_hRequestQueueThread == nullptr && !IsStatusConnecting(m_iStatus)) { + m_iStatus = ID_STATUS_CONNECTING; + m_isTerminated = false; + ForkThread(&CSteamProto::RequestQueueThread); + + Login(); + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); + } + else if (IsOnline()) { + m_iStatus = new_status; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); + } + return 0; +} + +void CSteamProto::GetAwayMsgThread(void *arg) +{ + // Maybe not needed, but better to be sure that this won't happen faster than core handling return value of GetAwayMsg() + Sleep(50); + + MCONTACT hContact = (UINT_PTR)arg; + CMStringW message(db_get_wsm(hContact, "CList", "StatusMsg")); + + // if contact has no status message, get xstatus message + if (message.IsEmpty()) { + ptrW xStatusName(getWStringA(hContact, "XStatusName")); + ptrW xStatusMsg(getWStringA(hContact, "XStatusMsg")); + + if (xStatusName) + message.AppendFormat(L"%s: %s", xStatusName.get(), xStatusMsg.get()); + else + message.Append(xStatusMsg); + } + + ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)message.c_str()); +} + +HANDLE CSteamProto::GetAwayMsg(MCONTACT hContact) +{ + ForkThread(&CSteamProto::GetAwayMsgThread, (void*)hContact); + return (HANDLE)1; +} + +void CSteamProto::OnContactDeleted(MCONTACT hContact) +{ + // remove only authorized contacts + if (!getByte(hContact, "Auth", 0)) { + ptrA token(getStringA("TokenSecret")); + ptrA sessionId(getStringA("SessionID")); + ptrA steamId(getStringA("SteamID")); + char *who = getStringA(hContact, "SteamID"); + PushRequest(new RemoveFriendRequest(token, sessionId, steamId, who), &CSteamProto::OnFriendRemoved, (void*)who); + } +} diff --git a/protocols/Telegram/.gitignore b/protocols/Telegram/.gitignore index c3af857904..cc2225d405 100644 --- a/protocols/Telegram/.gitignore +++ b/protocols/Telegram/.gitignore @@ -1 +1 @@ -lib/ +lib/ diff --git a/protocols/Telegram/Telegram.vcxproj b/protocols/Telegram/Telegram.vcxproj index 9c1812a737..16b8bbd8a1 100644 --- a/protocols/Telegram/Telegram.vcxproj +++ b/protocols/Telegram/Telegram.vcxproj @@ -1,68 +1,68 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} - Telegram - - - - - - - - - - - - - Create - - - - - - .\tdlib\td;.\tdlib\td\td\generate\auto;%(AdditionalIncludeDirectories) - - - .\tdlib\lib\$(Configuration)32;%(AdditionalLibraryDirectories) - .\tdlib\lib\$(Configuration)64;%(AdditionalLibraryDirectories) - libcrypto.lib;libssl.lib;crypt32.lib;psapi.lib;Normaliz.lib;%(AdditionalDependencies) - - - - - - - - - - - {0c02e395-e73f-47e3-8b95-b7924c0c7a6a} - - - {e2a369cd-eda3-414f-8ad0-e732cd7ee68c} - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} + Telegram + + + + + + + + + + + + + Create + + + + + + .\tdlib\td;.\tdlib\td\td\generate\auto;%(AdditionalIncludeDirectories) + + + .\tdlib\lib\$(Configuration)32;%(AdditionalLibraryDirectories) + .\tdlib\lib\$(Configuration)64;%(AdditionalLibraryDirectories) + libcrypto.lib;libssl.lib;crypt32.lib;psapi.lib;Normaliz.lib;%(AdditionalDependencies) + + + + + + + + + + + {0c02e395-e73f-47e3-8b95-b7924c0c7a6a} + + + {e2a369cd-eda3-414f-8ad0-e732cd7ee68c} + + + + + + \ No newline at end of file diff --git a/protocols/Telegram/Telegram.vcxproj.filters b/protocols/Telegram/Telegram.vcxproj.filters index e6d8a2433a..528f29127c 100644 --- a/protocols/Telegram/Telegram.vcxproj.filters +++ b/protocols/Telegram/Telegram.vcxproj.filters @@ -1,60 +1,60 @@ - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - Resource Files - - + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + \ No newline at end of file diff --git a/protocols/Telegram/proto_telegram/CMakeLists.txt b/protocols/Telegram/proto_telegram/CMakeLists.txt index 223ee6af39..122641d52b 100644 --- a/protocols/Telegram/proto_telegram/CMakeLists.txt +++ b/protocols/Telegram/proto_telegram/CMakeLists.txt @@ -1,2 +1,2 @@ -set(TARGET Proto_Telegram) +set(TARGET Proto_Telegram) include(${CMAKE_SOURCE_DIR}/cmake/icons.cmake) \ No newline at end of file diff --git a/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj b/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj index 3eb5b57a48..715dbd2330 100644 --- a/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj +++ b/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj @@ -1,34 +1,34 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - Proto_Telegram - {129A4E61-45E8-E476-BD50-5E03BA80E9AD} - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Proto_Telegram + {129A4E61-45E8-E476-BD50-5E03BA80E9AD} + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj.filters b/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj.filters index 19bac99d33..7ae0743b90 100644 --- a/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj.filters +++ b/protocols/Telegram/proto_telegram/Proto_Telegram.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - - Header Files - - - - - Resource Files - - + + + + + + Header Files + + + + + Resource Files + + \ No newline at end of file diff --git a/protocols/Telegram/proto_telegram/res/Proto_Telegram.rc b/protocols/Telegram/proto_telegram/res/Proto_Telegram.rc index c72b18eb8c..201a96305c 100644 --- a/protocols/Telegram/proto_telegram/res/Proto_Telegram.rc +++ b/protocols/Telegram/proto_telegram/res/Proto_Telegram.rc @@ -1,71 +1,71 @@ -// 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" - -#endif // Russian (Russia) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// 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" + +#endif // Russian (Russia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Telegram/proto_telegram/src/resource.h b/protocols/Telegram/proto_telegram/src/resource.h index 12364e6a49..192c1b9f19 100644 --- a/protocols/Telegram/proto_telegram/src/resource.h +++ b/protocols/Telegram/proto_telegram/src/resource.h @@ -1,19 +1,19 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Proto_Telegram.rc -// -#define IDI_ICON1 105 -#define IDI_ICON2 104 -#define IDI_ICON3 128 -#define IDI_ICON4 159 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 106 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Proto_Telegram.rc +// +#define IDI_ICON1 105 +#define IDI_ICON2 104 +#define IDI_ICON3 128 +#define IDI_ICON4 159 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Telegram/res/resource.rc b/protocols/Telegram/res/resource.rc index 0aacaa94c4..8e8936badf 100644 --- a/protocols/Telegram/res/resource.rc +++ b/protocols/Telegram/res/resource.rc @@ -1,154 +1,154 @@ -// Microsoft Visual C++ generated resource script. -// -#include "..\src\resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// Russian (Russia) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) -LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT -#pragma code_page(1251) - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "..\\src\\resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // Russian (Russia) resources -///////////////////////////////////////////////////////////////////////////// - - -///////////////////////////////////////////////////////////////////////////// -// English (Neutral) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL -#pragma code_page(1252) - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_ACCMGRUI DIALOGEX 0, 0, 188, 144 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 400, 0, 0x0 -BEGIN - LTEXT "Phone number:",IDC_STATIC,0,6,89,10 - EDITTEXT IDC_PHONE,96,4,86,12,ES_AUTOHSCROLL - LTEXT "Default group:",IDC_STATIC,0,23,89,10 - EDITTEXT IDC_DEFGROUP,96,21,86,12,ES_AUTOHSCROLL - LTEXT "Device name:",IDC_STATIC,0,39,89,10 - EDITTEXT IDC_DEVICE_NAME,96,38,86,12,ES_AUTOHSCROLL - CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,57,182,10 -END - -IDD_OPTIONS DIALOGEX 0, 0, 305, 188 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - LTEXT "Phone number:",IDC_STATIC,5,6,79,10 - EDITTEXT IDC_PHONE,87,5,211,12,ES_AUTOHSCROLL - LTEXT "Default group:",IDC_STATIC,5,24,79,10 - EDITTEXT IDC_DEFGROUP,87,23,211,12,ES_AUTOHSCROLL - LTEXT "Device name:",IDC_STATIC,5,43,79,10 - EDITTEXT IDC_DEVICE_NAME,87,41,211,12,ES_AUTOHSCROLL - CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,64,294,10 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_TELEGRAM ICON "telegram.ico" - -IDI_PREMIUM ICON "premium.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_ACCMGRUI, DIALOG - BEGIN - END - - IDD_OPTIONS, DIALOG - BEGIN - END -END -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_ACCMGRUI AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_OPTIONS AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -#endif // English (Neutral) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// Microsoft Visual C++ generated resource script. +// +#include "..\src\resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Russian (Russia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT +#pragma code_page(1251) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\\src\\resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Russian (Russia) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (Neutral) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ACCMGRUI DIALOGEX 0, 0, 188, 144 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + LTEXT "Phone number:",IDC_STATIC,0,6,89,10 + EDITTEXT IDC_PHONE,96,4,86,12,ES_AUTOHSCROLL + LTEXT "Default group:",IDC_STATIC,0,23,89,10 + EDITTEXT IDC_DEFGROUP,96,21,86,12,ES_AUTOHSCROLL + LTEXT "Device name:",IDC_STATIC,0,39,89,10 + EDITTEXT IDC_DEVICE_NAME,96,38,86,12,ES_AUTOHSCROLL + CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,57,182,10 +END + +IDD_OPTIONS DIALOGEX 0, 0, 305, 188 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + LTEXT "Phone number:",IDC_STATIC,5,6,79,10 + EDITTEXT IDC_PHONE,87,5,211,12,ES_AUTOHSCROLL + LTEXT "Default group:",IDC_STATIC,5,24,79,10 + EDITTEXT IDC_DEFGROUP,87,23,211,12,ES_AUTOHSCROLL + LTEXT "Device name:",IDC_STATIC,5,43,79,10 + EDITTEXT IDC_DEVICE_NAME,87,41,211,12,ES_AUTOHSCROLL + CONTROL "Do not open chat windows on creation",IDC_HIDECHATS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,64,294,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_TELEGRAM ICON "telegram.ico" + +IDI_PREMIUM ICON "premium.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ACCMGRUI, DIALOG + BEGIN + END + + IDD_OPTIONS, DIALOG + BEGIN + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_ACCMGRUI AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Neutral) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Telegram/res/telegram.ico b/protocols/Telegram/res/telegram.ico index dd8dbdb7ad..45b58cead4 100644 Binary files a/protocols/Telegram/res/telegram.ico and b/protocols/Telegram/res/telegram.ico differ diff --git a/protocols/Telegram/src/auth.cpp b/protocols/Telegram/src/auth.cpp index 3778ff1039..043628ab19 100644 --- a/protocols/Telegram/src/auth.cpp +++ b/protocols/Telegram/src/auth.cpp @@ -1,126 +1,126 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" - -#include "../../../../miranda-private-keys/Telegram/api.h" - -/////////////////////////////////////////////////////////////////////////////// - -INT_PTR CALLBACK CMTProto::EnterPhoneCode(void *param) -{ - auto *ppro = (CMTProto *)param; - - ENTER_STRING es = {}; - es.szModuleName = ppro->m_szModuleName; - es.caption = TranslateT("Enter secret code sent to your phone"); - if (EnterString(&es)) { - ppro->SendQuery(new TD::checkAuthenticationCode(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); - mir_free(es.ptszResult); - } - else ppro->LogOut(); - return 0; -} - -INT_PTR CALLBACK CMTProto::EnterPassword(void *param) -{ - auto *ppro = (CMTProto *)param; - CMStringW wszTitle(TranslateT("Enter password")); - - auto *pAuth = (TD::authorizationStateWaitPassword *)ppro->pAuthState.get(); - if (!pAuth->password_hint_.empty()) - wszTitle.AppendFormat(TranslateT(" (hint: %s)"), Utf2T(pAuth->password_hint_.c_str()).get()); - - ENTER_STRING es = {}; - es.szModuleName = ppro->m_szModuleName; - es.caption = wszTitle; - es.type = ESF_PASSWORD; - if (EnterString(&es)) { - ppro->SendQuery(new TD::checkAuthenticationPassword(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); - mir_free(es.ptszResult); - } - else ppro->LogOut(); - return 0; -} - -void CMTProto::ProcessAuth(TD::updateAuthorizationState *pObj) -{ - pAuthState = std::move(pObj->authorization_state_); - switch (pAuthState->get_id()) { - case TD::authorizationStateWaitTdlibParameters::ID: - { - MFileVersion v; - char text[100]; - Miranda_GetFileVersion(&v); - mir_snprintf(text, "%d.%d.%d.%d", v[0], v[1], v[2], v[3]); - - CMStringW wszPath(GetProtoFolder()); - - auto *request = new TD::setTdlibParameters(); - request->database_directory_ = T2Utf(wszPath).get(); - request->use_message_database_ = false; - request->use_secret_chats_ = true; - request->api_id_ = MIRANDA_API_ID; - request->api_hash_ = MIRANDA_API_HASH; - request->system_language_code_ = "en"; - request->device_model_ = T2Utf(m_wszDeviceName).get(); - request->application_version_ = text; - request->enable_storage_optimizer_ = true; - SendQuery(request, &CMTProto::OnUpdateAuth); - } - break; - - case TD::authorizationStateWaitPhoneNumber::ID: - SendQuery(new TD::setAuthenticationPhoneNumber(_T2A(m_szOwnPhone).get(), nullptr), &CMTProto::OnUpdateAuth); - break; - - case TD::authorizationStateWaitCode::ID: - CallFunctionSync(EnterPhoneCode, this); - break; - - case TD::authorizationStateWaitPassword::ID: - CallFunctionSync(EnterPassword, this); - break; - - case TD::authorizationStateReady::ID: - OnLoggedIn(); - break; - - case TD::authorizationStateClosed::ID: - debugLogA("Connection terminated, exiting"); - LogOut(); - break; - } -} - -void CMTProto::OnUpdateAuth(td::ClientManager::Response &response) -{ - if (response.object->get_id() == TD::error::ID) { - auto *pError = (TD::error *)response.object.get(); - debugLogA("error happened: %s", to_string(*pError).c_str()); - - if (pError->message_ == "PHONE_CODE_EXPIRED") - Popup(0, TranslateT("Phone code expired"), TranslateT("Error")); - else if (pError->message_ == "INVALID_PHONE_CODE") - Popup(0, TranslateT("Invalid phone code"), TranslateT("Error")); - else if (pError->message_ == "PASSWORD_HASH_INVALID") - Popup(0, TranslateT("Invalid password"), TranslateT("Error")); - - pAuthState = std::move(nullptr); - LogOut(); - } -} +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" + +#include "../../../../miranda-private-keys/Telegram/api.h" + +/////////////////////////////////////////////////////////////////////////////// + +INT_PTR CALLBACK CMTProto::EnterPhoneCode(void *param) +{ + auto *ppro = (CMTProto *)param; + + ENTER_STRING es = {}; + es.szModuleName = ppro->m_szModuleName; + es.caption = TranslateT("Enter secret code sent to your phone"); + if (EnterString(&es)) { + ppro->SendQuery(new TD::checkAuthenticationCode(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); + mir_free(es.ptszResult); + } + else ppro->LogOut(); + return 0; +} + +INT_PTR CALLBACK CMTProto::EnterPassword(void *param) +{ + auto *ppro = (CMTProto *)param; + CMStringW wszTitle(TranslateT("Enter password")); + + auto *pAuth = (TD::authorizationStateWaitPassword *)ppro->pAuthState.get(); + if (!pAuth->password_hint_.empty()) + wszTitle.AppendFormat(TranslateT(" (hint: %s)"), Utf2T(pAuth->password_hint_.c_str()).get()); + + ENTER_STRING es = {}; + es.szModuleName = ppro->m_szModuleName; + es.caption = wszTitle; + es.type = ESF_PASSWORD; + if (EnterString(&es)) { + ppro->SendQuery(new TD::checkAuthenticationPassword(_T2A(es.ptszResult).get()), &CMTProto::OnUpdateAuth); + mir_free(es.ptszResult); + } + else ppro->LogOut(); + return 0; +} + +void CMTProto::ProcessAuth(TD::updateAuthorizationState *pObj) +{ + pAuthState = std::move(pObj->authorization_state_); + switch (pAuthState->get_id()) { + case TD::authorizationStateWaitTdlibParameters::ID: + { + MFileVersion v; + char text[100]; + Miranda_GetFileVersion(&v); + mir_snprintf(text, "%d.%d.%d.%d", v[0], v[1], v[2], v[3]); + + CMStringW wszPath(GetProtoFolder()); + + auto *request = new TD::setTdlibParameters(); + request->database_directory_ = T2Utf(wszPath).get(); + request->use_message_database_ = false; + request->use_secret_chats_ = true; + request->api_id_ = MIRANDA_API_ID; + request->api_hash_ = MIRANDA_API_HASH; + request->system_language_code_ = "en"; + request->device_model_ = T2Utf(m_wszDeviceName).get(); + request->application_version_ = text; + request->enable_storage_optimizer_ = true; + SendQuery(request, &CMTProto::OnUpdateAuth); + } + break; + + case TD::authorizationStateWaitPhoneNumber::ID: + SendQuery(new TD::setAuthenticationPhoneNumber(_T2A(m_szOwnPhone).get(), nullptr), &CMTProto::OnUpdateAuth); + break; + + case TD::authorizationStateWaitCode::ID: + CallFunctionSync(EnterPhoneCode, this); + break; + + case TD::authorizationStateWaitPassword::ID: + CallFunctionSync(EnterPassword, this); + break; + + case TD::authorizationStateReady::ID: + OnLoggedIn(); + break; + + case TD::authorizationStateClosed::ID: + debugLogA("Connection terminated, exiting"); + LogOut(); + break; + } +} + +void CMTProto::OnUpdateAuth(td::ClientManager::Response &response) +{ + if (response.object->get_id() == TD::error::ID) { + auto *pError = (TD::error *)response.object.get(); + debugLogA("error happened: %s", to_string(*pError).c_str()); + + if (pError->message_ == "PHONE_CODE_EXPIRED") + Popup(0, TranslateT("Phone code expired"), TranslateT("Error")); + else if (pError->message_ == "INVALID_PHONE_CODE") + Popup(0, TranslateT("Invalid phone code"), TranslateT("Error")); + else if (pError->message_ == "PASSWORD_HASH_INVALID") + Popup(0, TranslateT("Invalid password"), TranslateT("Error")); + + pAuthState = std::move(nullptr); + LogOut(); + } +} diff --git a/protocols/Telegram/src/avatars.cpp b/protocols/Telegram/src/avatars.cpp index b21ddf81d0..bd24079f5f 100644 --- a/protocols/Telegram/src/avatars.cpp +++ b/protocols/Telegram/src/avatars.cpp @@ -1,95 +1,95 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" - -INT_PTR CMTProto::SvcGetAvatarCaps(WPARAM wParam, LPARAM lParam) -{ - switch (wParam) { - case AF_MAXSIZE: - ((POINT *)lParam)->x = 160; - ((POINT *)lParam)->y = 160; - break; - - case AF_MAXFILESIZE: - return 32000; - - case AF_PROPORTION: - return PIP_SQUARE; - - case AF_FORMATSUPPORTED: - case AF_ENABLED: - case AF_DONTNEEDDELAYS: - case AF_FETCHIFPROTONOTVISIBLE: - case AF_FETCHIFCONTACTOFFLINE: - return 1; - } - return 0; -} - -INT_PTR CMTProto::SvcGetAvatarInfo(WPARAM, LPARAM lParam) -{ - auto *pai = (PROTO_AVATAR_INFORMATION *)lParam; - - ptrW wszPath(getWStringA(pai->hContact, DBKEY_AVATAR_PATH)); - if (wszPath == nullptr) - return GAIR_NOAVATAR; - - pai->format = getByte(pai->hContact, DBKEY_AVATAR_TYPE, PA_FORMAT_JPEG); - wcsncpy_s(pai->filename, wszPath, _TRUNCATE); - - if (::_waccess(pai->filename, 0) == 0) - return GAIR_SUCCESS; - - debugLogA("No avatar"); - return GAIR_NOAVATAR; -} - -INT_PTR CMTProto::SvcGetMyAvatar(WPARAM, LPARAM) -{ - return 1; -} - -INT_PTR CMTProto::SvcSetMyAvatar(WPARAM, LPARAM) -{ - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessFile(TD::updateFile *pObj) -{ - if (auto *pFile = pObj->file_.get()) { - if (!pFile->local_->is_downloading_completed_) - return; - - for (auto &it : m_arUsers) { - if (it->szAvatarHash == pFile->remote_->unique_id_.c_str()) { - PROTO_AVATAR_INFORMATION pai; - wcsncpy_s(pai.filename, Utf2T(pFile->local_->path_.c_str()), _TRUNCATE); - pai.hContact = it->hContact; - pai.format = ProtoGetAvatarFileFormat(pai.filename); - - setByte(pai.hContact, DBKEY_AVATAR_TYPE, pai.format); - setWString(pai.hContact, DBKEY_AVATAR_PATH, pai.filename); - - ProtoBroadcastAck(it->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &pai); - break; - } - } - } -} +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" + +INT_PTR CMTProto::SvcGetAvatarCaps(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) { + case AF_MAXSIZE: + ((POINT *)lParam)->x = 160; + ((POINT *)lParam)->y = 160; + break; + + case AF_MAXFILESIZE: + return 32000; + + case AF_PROPORTION: + return PIP_SQUARE; + + case AF_FORMATSUPPORTED: + case AF_ENABLED: + case AF_DONTNEEDDELAYS: + case AF_FETCHIFPROTONOTVISIBLE: + case AF_FETCHIFCONTACTOFFLINE: + return 1; + } + return 0; +} + +INT_PTR CMTProto::SvcGetAvatarInfo(WPARAM, LPARAM lParam) +{ + auto *pai = (PROTO_AVATAR_INFORMATION *)lParam; + + ptrW wszPath(getWStringA(pai->hContact, DBKEY_AVATAR_PATH)); + if (wszPath == nullptr) + return GAIR_NOAVATAR; + + pai->format = getByte(pai->hContact, DBKEY_AVATAR_TYPE, PA_FORMAT_JPEG); + wcsncpy_s(pai->filename, wszPath, _TRUNCATE); + + if (::_waccess(pai->filename, 0) == 0) + return GAIR_SUCCESS; + + debugLogA("No avatar"); + return GAIR_NOAVATAR; +} + +INT_PTR CMTProto::SvcGetMyAvatar(WPARAM, LPARAM) +{ + return 1; +} + +INT_PTR CMTProto::SvcSetMyAvatar(WPARAM, LPARAM) +{ + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessFile(TD::updateFile *pObj) +{ + if (auto *pFile = pObj->file_.get()) { + if (!pFile->local_->is_downloading_completed_) + return; + + for (auto &it : m_arUsers) { + if (it->szAvatarHash == pFile->remote_->unique_id_.c_str()) { + PROTO_AVATAR_INFORMATION pai; + wcsncpy_s(pai.filename, Utf2T(pFile->local_->path_.c_str()), _TRUNCATE); + pai.hContact = it->hContact; + pai.format = ProtoGetAvatarFileFormat(pai.filename); + + setByte(pai.hContact, DBKEY_AVATAR_TYPE, pai.format); + setWString(pai.hContact, DBKEY_AVATAR_PATH, pai.filename); + + ProtoBroadcastAck(it->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &pai); + break; + } + } + } +} diff --git a/protocols/Telegram/src/main.cpp b/protocols/Telegram/src/main.cpp index 6af8517ab7..c00b87536d 100644 --- a/protocols/Telegram/src/main.cpp +++ b/protocols/Telegram/src/main.cpp @@ -1,52 +1,52 @@ -#include "stdafx.h" - -int hLangpack; -CMPlugin g_plugin; - -#pragma comment(lib, "tdactor.lib") -#pragma comment(lib, "tdcore.lib") -#pragma comment(lib, "tddb.lib") -#pragma comment(lib, "tdlib.lib") -#pragma comment(lib, "tdnet.lib") -#pragma comment(lib, "tdutils.lib") - -///////////////////////////////////////////////////////////////////////////////////////// - -PLUGININFOEX pluginInfo = -{ - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - // {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} - {0xae708252, 0xdf8, 0x42ba, {0x9e, 0xf9, 0x9a, 0xcc, 0x3, 0x8e, 0xed, 0xa7}} -}; - -CMPlugin::CMPlugin() : - ACCPROTOPLUGIN("Telegram", pluginInfo) -{ - SetUniqueId(DBKEY_ID); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// - -static IconItem iconList[] = -{ - { LPGEN("Premium user"), "premuim", IDI_PREMIUM }, -}; - -int CMPlugin::Load() -{ - registerIcon("Protocols/Telegram", iconList, "tg"); - - m_hIcon = ExtraIcon_RegisterIcolib("tg_premium", "Telegram Premium User", getIconHandle(IDI_PREMIUM)); - return 0; -} +#include "stdafx.h" + +int hLangpack; +CMPlugin g_plugin; + +#pragma comment(lib, "tdactor.lib") +#pragma comment(lib, "tdcore.lib") +#pragma comment(lib, "tddb.lib") +#pragma comment(lib, "tdlib.lib") +#pragma comment(lib, "tdnet.lib") +#pragma comment(lib, "tdutils.lib") + +///////////////////////////////////////////////////////////////////////////////////////// + +PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7} + {0xae708252, 0xdf8, 0x42ba, {0x9e, 0xf9, 0x9a, 0xcc, 0x3, 0x8e, 0xed, 0xa7}} +}; + +CMPlugin::CMPlugin() : + ACCPROTOPLUGIN("Telegram", pluginInfo) +{ + SetUniqueId(DBKEY_ID); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; + +///////////////////////////////////////////////////////////////////////////////////////// + +static IconItem iconList[] = +{ + { LPGEN("Premium user"), "premuim", IDI_PREMIUM }, +}; + +int CMPlugin::Load() +{ + registerIcon("Protocols/Telegram", iconList, "tg"); + + m_hIcon = ExtraIcon_RegisterIcolib("tg_premium", "Telegram Premium User", getIconHandle(IDI_PREMIUM)); + return 0; +} diff --git a/protocols/Telegram/src/mt_proto.cpp b/protocols/Telegram/src/mt_proto.cpp index a58c3ded1d..2728aa94fb 100644 --- a/protocols/Telegram/src/mt_proto.cpp +++ b/protocols/Telegram/src/mt_proto.cpp @@ -1,198 +1,198 @@ -#include "stdafx.h" - -static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) -{ - if (p1->requestId == p2->requestId) - return 0; - - return (p1->requestId < p2->requestId) ? -1 : 1; -} - -static int CompareUsers(const TG_USER *p1, const TG_USER *p2) -{ - if (p1->id == p2->id) - return 0; - - return (p1->id < p2->id) ? -1 : 1; -} - -CMTProto::CMTProto(const char* protoName, const wchar_t* userName) : - PROTO(protoName, userName), - m_impl(*this), - m_arUsers(10, CompareUsers), - m_arRequests(10, CompareRequests), - m_szOwnPhone(this, "Phone"), - m_wszDeviceName(this, "DeviceName", L"Miranda"), - m_wszDefaultGroup(this, "DefaultGroup", L"Telegram"), - m_bUsePopups(this, "UsePopups", true), - m_bHideGroupchats(this, "HideChats", true) -{ - m_iOwnId = _atoi64(getMStringA(DBKEY_ID)); - - CreateProtoService(PS_CREATEACCMGRUI, &CMTProto::SvcCreateAccMgrUI); - CreateProtoService(PS_GETAVATARCAPS, &CMTProto::SvcGetAvatarCaps); - CreateProtoService(PS_GETAVATARINFO, &CMTProto::SvcGetAvatarInfo); - CreateProtoService(PS_GETMYAVATAR, &CMTProto::SvcGetMyAvatar); - CreateProtoService(PS_SETMYAVATAR, &CMTProto::SvcSetMyAvatar); - - HookProtoEvent(ME_OPT_INITIALISE, &CMTProto::OnOptionsInit); - HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CMTProto::OnDbMarkedRead); - - // default contacts group - if (m_wszDefaultGroup == NULL) - m_wszDefaultGroup = mir_wstrdup(L"WhatsApp"); - m_iBaseGroup = Clist_GroupCreate(0, m_wszDefaultGroup); - - // Create standard network connection - NETLIBUSER nlu = {}; - nlu.flags = NUF_UNICODE; - nlu.szSettingsModule = m_szModuleName; - nlu.szDescriptiveName.w = m_tszUserName; - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - // groupchat initialization - GCREGISTER gcr = {}; - gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; - gcr.ptszDispName = m_tszUserName; - gcr.pszModule = m_szModuleName; - Chat_Register(&gcr); - - // HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); - // HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); -} - -CMTProto::~CMTProto() -{ -} - -void CMTProto::OnModulesLoaded() -{ - CMStringA szId(getMStringA(DBKEY_ID)); - if (!szId.IsEmpty()) - m_arUsers.insert(new TG_USER(_atoi64(szId.c_str()), 0)); - - for (auto &cc : AccContacts()) { - bool isGroupChat = isChatRoom(cc); - szId = getMStringA(cc, isGroupChat ? "ChatRoomID" : DBKEY_ID); - if (!szId.IsEmpty()) { - auto *pUser = new TG_USER(_atoi64(szId.c_str()), cc, isGroupChat); - pUser->szAvatarHash = getMStringA(cc, DBKEY_AVATAR_HASH); - m_arUsers.insert(pUser); - } - } -} - -void CMTProto::OnShutdown() -{ - m_bTerminated = true; -} - -void CMTProto::OnErase() -{ - m_bUnregister = true; - ServerThread(0); - - DeleteDirectoryTreeW(GetProtoFolder(), false); -} - -int CMTProto::OnDbMarkedRead(WPARAM hContact, LPARAM hDbEvent) -{ - if (!hContact) - return 0; - - // filter out only events of my protocol - const char *szProto = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(szProto, m_szModuleName)) - return 0; - - ptrA userId(getStringA(hContact, DBKEY_ID)); - if (userId) { - DBEVENTINFO dbei = {}; - db_event_get(hDbEvent, &dbei); - if (dbei.szId) { - mir_cslock lck(m_csMarkRead); - if (m_markContact) { - if (m_markContact != hContact) - SendMarkRead(); - - m_impl.m_markRead.Stop(); - } - - m_markContact = hContact; - m_markIds.push_back(_atoi64(dbei.szId)); - m_impl.m_markRead.Start(500); - } - } - - return 0; -} - -INT_PTR CMTProto::GetCaps(int type, MCONTACT) -{ - switch (type) { - case PFLAGNUM_1: - return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; - case PFLAGNUM_2: - return PF2_ONLINE; - case PFLAGNUM_3: - return 0; - case PFLAGNUM_4: - return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; - case PFLAGNUM_5: - return 0; - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)L"Phone"; - } - return 0; -} - -int CMTProto::SendMsg(MCONTACT hContact, int, const char *pszMessage) -{ - ptrA szId(getStringA(hContact, DBKEY_ID)); - if (szId == nullptr) - return 0; - - return SendTextMessage(_atoi64(szId), pszMessage); -} - -int CMTProto::SetStatus(int iNewStatus) -{ - if (m_iDesiredStatus == iNewStatus) - return 0; - - int oldStatus = m_iStatus; - - // Routing statuses not supported by Telegram - switch (iNewStatus) { - case ID_STATUS_OFFLINE: - m_iDesiredStatus = iNewStatus; - break; - - case ID_STATUS_ONLINE: - case ID_STATUS_FREECHAT: - default: - m_iDesiredStatus = ID_STATUS_ONLINE; - break; - } - - if (m_iDesiredStatus == ID_STATUS_OFFLINE) { - if (isRunning()) - SendQuery(new TD::close()); - - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else if (!isRunning() && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - ForkThread(&CMTProto::ServerThread); - } - else if (isRunning()) { - m_iStatus = m_iDesiredStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - return 0; -} +#include "stdafx.h" + +static int CompareRequests(const TG_REQUEST_BASE *p1, const TG_REQUEST_BASE *p2) +{ + if (p1->requestId == p2->requestId) + return 0; + + return (p1->requestId < p2->requestId) ? -1 : 1; +} + +static int CompareUsers(const TG_USER *p1, const TG_USER *p2) +{ + if (p1->id == p2->id) + return 0; + + return (p1->id < p2->id) ? -1 : 1; +} + +CMTProto::CMTProto(const char* protoName, const wchar_t* userName) : + PROTO(protoName, userName), + m_impl(*this), + m_arUsers(10, CompareUsers), + m_arRequests(10, CompareRequests), + m_szOwnPhone(this, "Phone"), + m_wszDeviceName(this, "DeviceName", L"Miranda"), + m_wszDefaultGroup(this, "DefaultGroup", L"Telegram"), + m_bUsePopups(this, "UsePopups", true), + m_bHideGroupchats(this, "HideChats", true) +{ + m_iOwnId = _atoi64(getMStringA(DBKEY_ID)); + + CreateProtoService(PS_CREATEACCMGRUI, &CMTProto::SvcCreateAccMgrUI); + CreateProtoService(PS_GETAVATARCAPS, &CMTProto::SvcGetAvatarCaps); + CreateProtoService(PS_GETAVATARINFO, &CMTProto::SvcGetAvatarInfo); + CreateProtoService(PS_GETMYAVATAR, &CMTProto::SvcGetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &CMTProto::SvcSetMyAvatar); + + HookProtoEvent(ME_OPT_INITIALISE, &CMTProto::OnOptionsInit); + HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CMTProto::OnDbMarkedRead); + + // default contacts group + if (m_wszDefaultGroup == NULL) + m_wszDefaultGroup = mir_wstrdup(L"WhatsApp"); + m_iBaseGroup = Clist_GroupCreate(0, m_wszDefaultGroup); + + // Create standard network connection + NETLIBUSER nlu = {}; + nlu.flags = NUF_UNICODE; + nlu.szSettingsModule = m_szModuleName; + nlu.szDescriptiveName.w = m_tszUserName; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + // groupchat initialization + GCREGISTER gcr = {}; + gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; + gcr.ptszDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + Chat_Register(&gcr); + + // HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); + // HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); +} + +CMTProto::~CMTProto() +{ +} + +void CMTProto::OnModulesLoaded() +{ + CMStringA szId(getMStringA(DBKEY_ID)); + if (!szId.IsEmpty()) + m_arUsers.insert(new TG_USER(_atoi64(szId.c_str()), 0)); + + for (auto &cc : AccContacts()) { + bool isGroupChat = isChatRoom(cc); + szId = getMStringA(cc, isGroupChat ? "ChatRoomID" : DBKEY_ID); + if (!szId.IsEmpty()) { + auto *pUser = new TG_USER(_atoi64(szId.c_str()), cc, isGroupChat); + pUser->szAvatarHash = getMStringA(cc, DBKEY_AVATAR_HASH); + m_arUsers.insert(pUser); + } + } +} + +void CMTProto::OnShutdown() +{ + m_bTerminated = true; +} + +void CMTProto::OnErase() +{ + m_bUnregister = true; + ServerThread(0); + + DeleteDirectoryTreeW(GetProtoFolder(), false); +} + +int CMTProto::OnDbMarkedRead(WPARAM hContact, LPARAM hDbEvent) +{ + if (!hContact) + return 0; + + // filter out only events of my protocol + const char *szProto = Proto_GetBaseAccountName(hContact); + if (mir_strcmp(szProto, m_szModuleName)) + return 0; + + ptrA userId(getStringA(hContact, DBKEY_ID)); + if (userId) { + DBEVENTINFO dbei = {}; + db_event_get(hDbEvent, &dbei); + if (dbei.szId) { + mir_cslock lck(m_csMarkRead); + if (m_markContact) { + if (m_markContact != hContact) + SendMarkRead(); + + m_impl.m_markRead.Stop(); + } + + m_markContact = hContact; + m_markIds.push_back(_atoi64(dbei.szId)); + m_impl.m_markRead.Start(500); + } + } + + return 0; +} + +INT_PTR CMTProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_3: + return 0; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; + case PFLAGNUM_5: + return 0; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)L"Phone"; + } + return 0; +} + +int CMTProto::SendMsg(MCONTACT hContact, int, const char *pszMessage) +{ + ptrA szId(getStringA(hContact, DBKEY_ID)); + if (szId == nullptr) + return 0; + + return SendTextMessage(_atoi64(szId), pszMessage); +} + +int CMTProto::SetStatus(int iNewStatus) +{ + if (m_iDesiredStatus == iNewStatus) + return 0; + + int oldStatus = m_iStatus; + + // Routing statuses not supported by Telegram + switch (iNewStatus) { + case ID_STATUS_OFFLINE: + m_iDesiredStatus = iNewStatus; + break; + + case ID_STATUS_ONLINE: + case ID_STATUS_FREECHAT: + default: + m_iDesiredStatus = ID_STATUS_ONLINE; + break; + } + + if (m_iDesiredStatus == ID_STATUS_OFFLINE) { + if (isRunning()) + SendQuery(new TD::close()); + + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else if (!isRunning() && !IsStatusConnecting(m_iStatus)) { + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + ForkThread(&CMTProto::ServerThread); + } + else if (isRunning()) { + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + return 0; +} diff --git a/protocols/Telegram/src/mt_proto.h b/protocols/Telegram/src/mt_proto.h index 8bc5cc3c66..bf5c9e667b 100644 --- a/protocols/Telegram/src/mt_proto.h +++ b/protocols/Telegram/src/mt_proto.h @@ -1,209 +1,209 @@ -#pragma once - -#define DBKEY_ID "id" - -#define DBKEY_AVATAR_HASH "AvatarHash" -#define DBKEY_AVATAR_PATH "AvatarPath" -#define DBKEY_AVATAR_TYPE "AvatarType" - -class CMTProto; -typedef void (CMTProto:: *TG_QUERY_HANDLER)(td::ClientManager::Response &response); -typedef void (CMTProto:: *TG_QUERY_HANDLER_FULL)(td::ClientManager::Response &response, void *pUserInfo); - -struct TG_REQUEST_BASE -{ - TG_REQUEST_BASE(td::ClientManager::RequestId _1) : - requestId(_1) - {} - - virtual ~TG_REQUEST_BASE() - {} - - td::ClientManager::RequestId requestId; - - virtual void Execute(CMTProto *ppro, td::ClientManager::Response &response) = 0; -}; - -struct TG_REQUEST : public TG_REQUEST_BASE -{ - TG_REQUEST(td::ClientManager::RequestId _1, TG_QUERY_HANDLER _2) : - TG_REQUEST_BASE(_1), - pHandler(_2) - {} - - TG_QUERY_HANDLER pHandler; - - void Execute(CMTProto *ppro, td::ClientManager::Response &response) override - { - (ppro->*pHandler)(response); - } -}; - -struct TG_REQUEST_FULL : public TG_REQUEST_BASE -{ - TG_REQUEST_FULL(td::ClientManager::RequestId _1, TG_QUERY_HANDLER_FULL _2, void *_3) : - TG_REQUEST_BASE(_1), - pHandler(_2), - pUserInfo(_3) - {} - - TG_QUERY_HANDLER_FULL pHandler; - void *pUserInfo; - - void Execute(CMTProto *ppro, td::ClientManager::Response &response) override - { - (ppro->*pHandler)(response, pUserInfo); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct TG_USER -{ - TG_USER(uint64_t _1, MCONTACT _2, bool _3 = false) : - id(_1), - hContact(_2), - isGroupChat(_3) - {} - - uint64_t id; - MCONTACT hContact; - bool isGroupChat; - CMStringA szAvatarHash; - time_t m_timer1 = 0, m_timer2 = 0; -}; - -class CMTProto : public PROTO -{ - class CProtoImpl - { - friend class CMTProto; - CMTProto &m_proto; - - CTimer m_keepAlive, m_markRead; - void OnKeepAlive(CTimer *) - { m_proto.SendKeepAlive(); - } - - void OnMarkRead(CTimer *) - { m_proto.SendMarkRead(); - } - - CProtoImpl(CMTProto &pro) : - m_proto(pro), - m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), - m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)+1) - { - m_markRead.OnEvent = Callback(this, &CProtoImpl::OnMarkRead); - m_keepAlive.OnEvent = Callback(this, &CProtoImpl::OnKeepAlive); - } - } m_impl; - - bool __forceinline isRunning() const - { return m_pClientMmanager != nullptr; - } - - std::unique_ptr m_pClientMmanager; - TD::object_ptr pAuthState; - - mir_cs m_csMarkRead; - MCONTACT m_markContact = 0; - TD::array m_markIds; - - bool m_bAuthorized, m_bTerminated, m_bUnregister = false; - int32_t m_iClientId, m_iMsgId; - uint64_t m_iQueryId; - - OBJLIST m_arRequests; - - static INT_PTR CALLBACK EnterPhoneCode(void *param); - static INT_PTR CALLBACK EnterPassword(void *param); - - CMStringW GetProtoFolder() const - { return CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName); - } - - void OnEndSession(td::ClientManager::Response &response); - void OnSendMessage(td::ClientManager::Response &response, void *pUserInfo); - void OnUpdateAuth(td::ClientManager::Response &response); - - void LogOut(void); - void OnLoggedIn(void); - void ProcessResponse(td::ClientManager::Response); - - void SendKeepAlive(void); - void SendMarkRead(void); - void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler = nullptr); - void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo); - int SendTextMessage(uint64_t chatId, const char *pszMessage); - - void ProcessAuth(TD::updateAuthorizationState *pObj); - void ProcessChat(TD::updateNewChat *pObj); - void ProcessChatPosition(TD::updateChatPosition *pObj); - void ProcessFile(TD::updateFile *pObj); - void ProcessGroups(TD::updateChatFilters *pObj); - void ProcessMarkRead(TD::updateChatReadInbox *pObj); - void ProcessMessage(TD::updateNewMessage *pObj); - void ProcessStatus(TD::updateUserStatus *pObj); - void ProcessUser(TD::updateUser *pObj); - - void UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str); - - // Users - int64_t m_iOwnId; - MGROUP m_iBaseGroup; - OBJLIST m_arUsers; - - TG_USER* FindUser(uint64_t id); - TG_USER* AddUser(uint64_t id, bool bIsChat); - - // Popups - HANDLE m_hPopupClass; - - void InitPopups(void); - void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); - -public: - ////////////////////////////////////////////////////////////////////////////////////// - // Ctors - - CMTProto(const char *protoName, const wchar_t *userName); - ~CMTProto(); - - ////////////////////////////////////////////////////////////////////////////////////// - // Virtual functions - - INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; - - int SendMsg(MCONTACT hContact, int flags, const char *pszMessage) override; - int SetStatus(int iNewStatus) override; - - void OnModulesLoaded() override; - void OnShutdown() override; - void OnErase() override; - - // Services ////////////////////////////////////////////////////////////////////////// - - INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetAvatarCaps(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetAvatarInfo(WPARAM, LPARAM); - INT_PTR __cdecl SvcGetMyAvatar(WPARAM, LPARAM); - INT_PTR __cdecl SvcSetMyAvatar(WPARAM, LPARAM); - - // Events //////////////////////////////////////////////////////////////////////////// - - int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnDbMarkedRead(WPARAM, LPARAM); - - // Options /////////////////////////////////////////////////////////////////////////// - - CMOption m_szOwnPhone; // our own phone number - CMOption m_wszDefaultGroup; // clist group to store contacts - CMOption m_wszDeviceName; // how do you see this session in Device List - CMOption m_bHideGroupchats; // do not open chat windows on creation - CMOption m_bUsePopups; - - // Processing Threads //////////////////////////////////////////////////////////////// - - void __cdecl ServerThread(void *); -}; +#pragma once + +#define DBKEY_ID "id" + +#define DBKEY_AVATAR_HASH "AvatarHash" +#define DBKEY_AVATAR_PATH "AvatarPath" +#define DBKEY_AVATAR_TYPE "AvatarType" + +class CMTProto; +typedef void (CMTProto:: *TG_QUERY_HANDLER)(td::ClientManager::Response &response); +typedef void (CMTProto:: *TG_QUERY_HANDLER_FULL)(td::ClientManager::Response &response, void *pUserInfo); + +struct TG_REQUEST_BASE +{ + TG_REQUEST_BASE(td::ClientManager::RequestId _1) : + requestId(_1) + {} + + virtual ~TG_REQUEST_BASE() + {} + + td::ClientManager::RequestId requestId; + + virtual void Execute(CMTProto *ppro, td::ClientManager::Response &response) = 0; +}; + +struct TG_REQUEST : public TG_REQUEST_BASE +{ + TG_REQUEST(td::ClientManager::RequestId _1, TG_QUERY_HANDLER _2) : + TG_REQUEST_BASE(_1), + pHandler(_2) + {} + + TG_QUERY_HANDLER pHandler; + + void Execute(CMTProto *ppro, td::ClientManager::Response &response) override + { + (ppro->*pHandler)(response); + } +}; + +struct TG_REQUEST_FULL : public TG_REQUEST_BASE +{ + TG_REQUEST_FULL(td::ClientManager::RequestId _1, TG_QUERY_HANDLER_FULL _2, void *_3) : + TG_REQUEST_BASE(_1), + pHandler(_2), + pUserInfo(_3) + {} + + TG_QUERY_HANDLER_FULL pHandler; + void *pUserInfo; + + void Execute(CMTProto *ppro, td::ClientManager::Response &response) override + { + (ppro->*pHandler)(response, pUserInfo); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct TG_USER +{ + TG_USER(uint64_t _1, MCONTACT _2, bool _3 = false) : + id(_1), + hContact(_2), + isGroupChat(_3) + {} + + uint64_t id; + MCONTACT hContact; + bool isGroupChat; + CMStringA szAvatarHash; + time_t m_timer1 = 0, m_timer2 = 0; +}; + +class CMTProto : public PROTO +{ + class CProtoImpl + { + friend class CMTProto; + CMTProto &m_proto; + + CTimer m_keepAlive, m_markRead; + void OnKeepAlive(CTimer *) + { m_proto.SendKeepAlive(); + } + + void OnMarkRead(CTimer *) + { m_proto.SendMarkRead(); + } + + CProtoImpl(CMTProto &pro) : + m_proto(pro), + m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), + m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)+1) + { + m_markRead.OnEvent = Callback(this, &CProtoImpl::OnMarkRead); + m_keepAlive.OnEvent = Callback(this, &CProtoImpl::OnKeepAlive); + } + } m_impl; + + bool __forceinline isRunning() const + { return m_pClientMmanager != nullptr; + } + + std::unique_ptr m_pClientMmanager; + TD::object_ptr pAuthState; + + mir_cs m_csMarkRead; + MCONTACT m_markContact = 0; + TD::array m_markIds; + + bool m_bAuthorized, m_bTerminated, m_bUnregister = false; + int32_t m_iClientId, m_iMsgId; + uint64_t m_iQueryId; + + OBJLIST m_arRequests; + + static INT_PTR CALLBACK EnterPhoneCode(void *param); + static INT_PTR CALLBACK EnterPassword(void *param); + + CMStringW GetProtoFolder() const + { return CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName); + } + + void OnEndSession(td::ClientManager::Response &response); + void OnSendMessage(td::ClientManager::Response &response, void *pUserInfo); + void OnUpdateAuth(td::ClientManager::Response &response); + + void LogOut(void); + void OnLoggedIn(void); + void ProcessResponse(td::ClientManager::Response); + + void SendKeepAlive(void); + void SendMarkRead(void); + void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler = nullptr); + void SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo); + int SendTextMessage(uint64_t chatId, const char *pszMessage); + + void ProcessAuth(TD::updateAuthorizationState *pObj); + void ProcessChat(TD::updateNewChat *pObj); + void ProcessChatPosition(TD::updateChatPosition *pObj); + void ProcessFile(TD::updateFile *pObj); + void ProcessGroups(TD::updateChatFilters *pObj); + void ProcessMarkRead(TD::updateChatReadInbox *pObj); + void ProcessMessage(TD::updateNewMessage *pObj); + void ProcessStatus(TD::updateUserStatus *pObj); + void ProcessUser(TD::updateUser *pObj); + + void UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str); + + // Users + int64_t m_iOwnId; + MGROUP m_iBaseGroup; + OBJLIST m_arUsers; + + TG_USER* FindUser(uint64_t id); + TG_USER* AddUser(uint64_t id, bool bIsChat); + + // Popups + HANDLE m_hPopupClass; + + void InitPopups(void); + void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); + +public: + ////////////////////////////////////////////////////////////////////////////////////// + // Ctors + + CMTProto(const char *protoName, const wchar_t *userName); + ~CMTProto(); + + ////////////////////////////////////////////////////////////////////////////////////// + // Virtual functions + + INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; + + int SendMsg(MCONTACT hContact, int flags, const char *pszMessage) override; + int SetStatus(int iNewStatus) override; + + void OnModulesLoaded() override; + void OnShutdown() override; + void OnErase() override; + + // Services ////////////////////////////////////////////////////////////////////////// + + INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetAvatarCaps(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetAvatarInfo(WPARAM, LPARAM); + INT_PTR __cdecl SvcGetMyAvatar(WPARAM, LPARAM); + INT_PTR __cdecl SvcSetMyAvatar(WPARAM, LPARAM); + + // Events //////////////////////////////////////////////////////////////////////////// + + int __cdecl OnOptionsInit(WPARAM, LPARAM); + int __cdecl OnDbMarkedRead(WPARAM, LPARAM); + + // Options /////////////////////////////////////////////////////////////////////////// + + CMOption m_szOwnPhone; // our own phone number + CMOption m_wszDefaultGroup; // clist group to store contacts + CMOption m_wszDeviceName; // how do you see this session in Device List + CMOption m_bHideGroupchats; // do not open chat windows on creation + CMOption m_bUsePopups; + + // Processing Threads //////////////////////////////////////////////////////////////// + + void __cdecl ServerThread(void *); +}; diff --git a/protocols/Telegram/src/options.cpp b/protocols/Telegram/src/options.cpp index 9159d7304e..f3c5cbfb37 100644 --- a/protocols/Telegram/src/options.cpp +++ b/protocols/Telegram/src/options.cpp @@ -1,82 +1,82 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -class COptionsDlg : public CProtoDlgBase -{ - CCtrlCheck chkHideChats, chkUsePopups; - CCtrlEdit edtGroup, edtPhone, edtDeviceName; - ptrW m_wszOldGroup; - -public: - COptionsDlg(CMTProto *ppro, int iDlgID, bool bFullDlg) : - CProtoDlgBase(ppro, iDlgID), - chkUsePopups(this, IDC_POPUPS), - chkHideChats(this, IDC_HIDECHATS), - edtPhone(this, IDC_PHONE), - edtGroup(this, IDC_DEFGROUP), - edtDeviceName(this, IDC_DEVICE_NAME), - m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) - { - CreateLink(edtPhone, ppro->m_szOwnPhone); - CreateLink(edtGroup, ppro->m_wszDefaultGroup); - CreateLink(edtDeviceName, ppro->m_wszDeviceName); - CreateLink(chkHideChats, ppro->m_bHideGroupchats); - - if (bFullDlg) - CreateLink(chkUsePopups, ppro->m_bUsePopups); - } - - bool OnApply() override - { - if (!mir_wstrlen(m_proto->m_szOwnPhone)) { - SetFocus(edtPhone.GetHwnd()); - return false; - } - - if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) - Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); - return true; - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CMTProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) -{ - auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false); - pDlg->SetParent((HWND)hwndParent); - pDlg->Create(); - return (INT_PTR)pDlg->GetHwnd(); -} - -int CMTProto::OnOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szTitle.w = m_tszUserName; - odp.flags = ODPF_UNICODE; - odp.szGroup.w = LPGENW("Network"); - - odp.position = 1; - odp.szTab.w = LPGENW("Account"); - odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// + +class COptionsDlg : public CProtoDlgBase +{ + CCtrlCheck chkHideChats, chkUsePopups; + CCtrlEdit edtGroup, edtPhone, edtDeviceName; + ptrW m_wszOldGroup; + +public: + COptionsDlg(CMTProto *ppro, int iDlgID, bool bFullDlg) : + CProtoDlgBase(ppro, iDlgID), + chkUsePopups(this, IDC_POPUPS), + chkHideChats(this, IDC_HIDECHATS), + edtPhone(this, IDC_PHONE), + edtGroup(this, IDC_DEFGROUP), + edtDeviceName(this, IDC_DEVICE_NAME), + m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup)) + { + CreateLink(edtPhone, ppro->m_szOwnPhone); + CreateLink(edtGroup, ppro->m_wszDefaultGroup); + CreateLink(edtDeviceName, ppro->m_wszDeviceName); + CreateLink(chkHideChats, ppro->m_bHideGroupchats); + + if (bFullDlg) + CreateLink(chkUsePopups, ppro->m_bUsePopups); + } + + bool OnApply() override + { + if (!mir_wstrlen(m_proto->m_szOwnPhone)) { + SetFocus(edtPhone.GetHwnd()); + return false; + } + + if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup)) + Clist_GroupCreate(0, m_proto->m_wszDefaultGroup); + return true; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CMTProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) +{ + auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false); + pDlg->SetParent((HWND)hwndParent); + pDlg->Create(); + return (INT_PTR)pDlg->GetHwnd(); +} + +int CMTProto::OnOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.szTitle.w = m_tszUserName; + odp.flags = ODPF_UNICODE; + odp.szGroup.w = LPGENW("Network"); + + odp.position = 1; + odp.szTab.w = LPGENW("Account"); + odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true); + g_plugin.addOptions(wParam, &odp); + return 0; +} diff --git a/protocols/Telegram/src/resource.h b/protocols/Telegram/src/resource.h index 87a094b2bc..9ea3641d02 100644 --- a/protocols/Telegram/src/resource.h +++ b/protocols/Telegram/src/resource.h @@ -1,25 +1,25 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by W:\miranda-ng\protocols\Telegram\res\resource.rc -// -#define IDI_TELEGRAM 100 -#define IDD_ACCMGRUI 101 -#define IDD_OPTIONS 102 -#define IDI_PREMIUM 103 -#define IDC_PHONE 1001 -#define IDC_DEFGROUP 1002 -#define IDC_HIDECHATS 1003 -#define IDC_POPUPS 1004 -#define IDC_DEFGROUP2 1004 -#define IDC_DEVICE_NAME 1005 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 106 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1006 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by W:\miranda-ng\protocols\Telegram\res\resource.rc +// +#define IDI_TELEGRAM 100 +#define IDD_ACCMGRUI 101 +#define IDD_OPTIONS 102 +#define IDI_PREMIUM 103 +#define IDC_PHONE 1001 +#define IDC_DEFGROUP 1002 +#define IDC_HIDECHATS 1003 +#define IDC_POPUPS 1004 +#define IDC_DEFGROUP2 1004 +#define IDC_DEVICE_NAME 1005 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Telegram/src/server.cpp b/protocols/Telegram/src/server.cpp index 24ad055fff..5828478802 100644 --- a/protocols/Telegram/src/server.cpp +++ b/protocols/Telegram/src/server.cpp @@ -1,419 +1,419 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" - -void CMTProto::OnEndSession(td::ClientManager::Response&) -{ - m_bTerminated = true; -} - -void __cdecl CMTProto::ServerThread(void *) -{ - m_bTerminated = m_bAuthorized = false; - - m_pClientMmanager = std::make_unique(); - m_iClientId = m_pClientMmanager->create_client_id(); - - SendQuery(new TD::getOption("version")); - - while (!m_bTerminated) { - ProcessResponse(m_pClientMmanager->receive(1)); - } - - m_pClientMmanager = std::move(nullptr); -} - -void CMTProto::LogOut() -{ - if (m_bTerminated) - return; - - debugLogA("CMTProto::OnLoggedOut"); - m_bTerminated = true; - m_bAuthorized = false; - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - setAllContactStatuses(ID_STATUS_OFFLINE, false); -} - -void CMTProto::OnLoggedIn() -{ - m_bAuthorized = true; - - debugLogA("CMTProto::OnLoggedIn"); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); - m_iStatus = m_iDesiredStatus; - - if (m_bUnregister) { - SendQuery(new TD::terminateSession()); - SendQuery(new TD::logOut(), &CMTProto::OnEndSession); - } - else SendQuery(new TD::getChats(td::tl::unique_ptr(), 1000)); -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::SendKeepAlive() -{ - time_t now = time(0); - - for (auto &it : m_arUsers) { - if (it->m_timer1 && now - it->m_timer1 > 600) { - it->m_timer1 = 0; - it->m_timer2 = now; - setWord(it->hContact, "Status", ID_STATUS_AWAY); - } - else if (it->m_timer2 && now - it->m_timer2 > 600) { - it->m_timer2 = 0; - setWord(it->hContact, "Status", ID_STATUS_OFFLINE); - } - } -} - -void CMTProto::SendMarkRead() -{ - m_impl.m_markRead.Stop(); - - mir_cslock lck(m_csMarkRead); - uint64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); - SendQuery(new TD::viewMessages(userId, 0, std::move(m_markIds), true)); - m_markContact = 0; -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessResponse(td::ClientManager::Response response) -{ - if (!response.object) - return; - - debugLogA("ProcessResponse: id=%d (%s)", int(response.request_id), to_string(response.object).c_str()); - - if (response.request_id) { - TG_REQUEST tmp(response.request_id, 0); - auto *p = m_arRequests.find(&tmp); - if (p) { - p->Execute(this, response); - m_arRequests.remove(p); - } - return; - } - - switch (response.object->get_id()) { - case TD::updateAuthorizationState::ID: - ProcessAuth((TD::updateAuthorizationState *)response.object.get()); - break; - - case TD::updateChatFilters::ID: - ProcessGroups((TD::updateChatFilters *)response.object.get()); - break; - - case TD::updateChatPosition::ID: - ProcessChatPosition((TD::updateChatPosition *)response.object.get()); - break; - - case TD::updateChatReadInbox::ID: - ProcessMarkRead((TD::updateChatReadInbox *)response.object.get()); - break; - - case TD::updateFile::ID: - ProcessFile((TD::updateFile*)response.object.get()); - break; - - case TD::updateNewChat::ID: - ProcessChat((TD::updateNewChat *)response.object.get()); - break; - - case TD::updateNewMessage::ID: - ProcessMessage((TD::updateNewMessage *)response.object.get()); - break; - - case TD::updateUserStatus::ID: - ProcessStatus((TD::updateUserStatus *)response.object.get()); - break; - - case TD::updateUser::ID: - ProcessUser((TD::updateUser *)response.object.get()); - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMTProto::OnSendMessage(td::ClientManager::Response &response, void *pUserInfo) -{ - if (!response.object) - return; - - if (response.object->get_id() != TD::message::ID) { - debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::message::ID); - return; - } - - auto *pMessage = ((TD::message *)response.object.get()); - auto *pUser = FindUser(pMessage->chat_id_); - if (pUser) { - char szMsgId[100]; - _i64toa(pMessage->id_, szMsgId, 10); - ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, pUserInfo, (LPARAM)szMsgId); - } -} - -int CMTProto::SendTextMessage(uint64_t chatId, const char *pszMessage) -{ - int ret = m_iMsgId++; - - auto pContent = TD::make_object(); - pContent->text_ = TD::make_object(); - pContent->text_->text_ = std::move(pszMessage); - - auto *pMessage = new TD::sendMessage(); - pMessage->chat_id_ = chatId; - pMessage->input_message_content_ = std::move(pContent); - SendQuery(pMessage, &CMTProto::OnSendMessage, (void*)ret); - - return ret; -} - -void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler) -{ - int queryId = ++m_iQueryId; - - auto szDescr = to_string(*pFunc); - debugLogA("Sending query %d:\n%s", queryId, szDescr.c_str()); - - m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); - - if (pHandler) - m_arRequests.insert(new TG_REQUEST(queryId, pHandler)); -} - -void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo) -{ - int queryId = ++m_iQueryId; - - auto szDescr = to_string(*pFunc); - debugLogA("Sending full query %d:\n%s", queryId, szDescr.c_str()); - - m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); - - if (pHandler) - m_arRequests.insert(new TG_REQUEST_FULL(queryId, pHandler, pUserInfo)); -} - -/////////////////////////////////////////////////////////////////////////////// - -void CMTProto::ProcessChat(TD::updateNewChat *pObj) -{ - auto &pChat = pObj->chat_; - if (pChat->type_->get_id() != TD::chatTypePrivate::ID) { - debugLogA("Only private chats are currently supported"); - return; - } - - if (auto *pUser = FindUser(pChat->id_)) - if (!pChat->title_.empty()) - setUString(pUser->hContact, "Nick", pChat->title_.c_str()); -} - -void CMTProto::ProcessChatPosition(TD::updateChatPosition *pObj) -{ - if (pObj->position_->get_id() != TD::chatPosition::ID) { - debugLogA("Unsupport position"); - return; - } - - auto *pUser = FindUser(pObj->chat_id_); - if (pUser == nullptr) { - debugLogA("Unknown chat, skipping"); - return; - } - - auto *pPos = (TD::chatPosition *)pObj->position_.get(); - if (pPos->list_) { - auto *pList = (TD::chatListFilter*)pPos->list_.get(); - - CMStringA szSetting(FORMAT, "ChatFilter%d", pList->chat_filter_id_); - CMStringW wszGroup(getMStringW(szSetting)); - if (!wszGroup.IsEmpty()) { - ptrW pwszExistingGroup(Clist_GetGroup(pUser->hContact)); - if (!pwszExistingGroup || !mir_wstrcmp(pwszExistingGroup, m_wszDefaultGroup)) { - CMStringW wszNewGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszGroup.c_str()); - Clist_SetGroup(pUser->hContact, wszNewGroup); - } - } - } -} - -void CMTProto::ProcessGroups(TD::updateChatFilters *pObj) -{ - for (auto &grp : pObj->chat_filters_) { - if (grp->icon_name_ != "Custom") - continue; - - CMStringA szSetting(FORMAT, "ChatFilter%d", grp->id_); - CMStringW wszOldValue(getMStringW(szSetting)); - Utf2T wszNewValue(grp->title_.c_str()); - if (wszOldValue.IsEmpty()) { - Clist_GroupCreate(m_iBaseGroup, wszNewValue); - setWString(szSetting, wszNewValue); - } - else if (wszOldValue != wszNewValue) { - CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t*)m_wszDefaultGroup, wszNewValue); - MGROUP oldGroup = Clist_GroupExists(wszFullGroup); - if (!oldGroup) - Clist_GroupCreate(m_iBaseGroup, wszFullGroup); - else - Clist_GroupRename(oldGroup, wszFullGroup); - setWString(szSetting, wszNewValue); - } - } -} - -void CMTProto::ProcessMarkRead(TD::updateChatReadInbox *pObj) -{ - auto *pUser = FindUser(pObj->chat_id_); - if (pUser == nullptr) { - debugLogA("message from unknown chat/user, ignored"); - return; - } - - char szId[100]; - _i64toa(pObj->last_read_inbox_message_id_, szId, 10); - MEVENT hLastRead = db_event_getById(m_szModuleName, szId); - if (hLastRead == 0) { - debugLogA("unknown event, ignored"); - return; - } - - bool bExit = false; - for (MEVENT hEvent = db_event_firstUnread(pUser->hContact); hEvent; hEvent = db_event_next(pUser->hContact, hEvent)) { - if (bExit) - break; - - bExit = (hEvent == hLastRead); - - DBEVENTINFO dbei = {}; - if (db_event_get(hEvent, &dbei)) - continue; - - if (!dbei.markedRead()) - db_event_markRead(pUser->hContact, hEvent); - } -} - -void CMTProto::ProcessMessage(TD::updateNewMessage *pObj) -{ - auto &pMessage = pObj->message_; - - auto *pUser = FindUser(pMessage->chat_id_); - if (pUser == nullptr) { - debugLogA("message from unknown chat/user, ignored"); - return; - } - - if (pUser->isGroupChat) { - debugLogA("message from group chat, ignored"); - return; - } - - CMStringA szText(getMessageText(pMessage->content_.get())); - if (szText.IsEmpty()) { - debugLogA("this message was not processed, ignored"); - return; - } - - char szId[100]; - _i64toa(pMessage->id_, szId, 10); - - PROTORECVEVENT pre = {}; - pre.szMessage = szText.GetBuffer(); - pre.szMsgId = szId; - pre.timestamp = pMessage->date_; - if (pMessage->sender_id_->get_id() == TD::messageSenderUser::ID) - if (((TD::messageSenderUser *)pMessage->sender_id_.get())->user_id_ == m_iOwnId) - pre.flags |= PREF_SENT; - ProtoChainRecvMsg(pUser->hContact, &pre); -} - -void CMTProto::ProcessStatus(TD::updateUserStatus *pObj) -{ - if (auto *pUser = FindUser(pObj->user_id_)) { - if (pObj->status_->get_id() == TD::userStatusOnline::ID) - setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); - else if (pObj->status_->get_id() == TD::userStatusOffline::ID) { - setWord(pUser->hContact, "Status", ID_STATUS_AWAY); - pUser->m_timer1 = time(0); - } - else debugLogA("!!!!! Unknown status packet, report it to the developers"); - } -} - -void CMTProto::ProcessUser(TD::updateUser *pObj) -{ - auto *pUser = pObj->user_.get(); - - if (pUser->phone_number_ == _T2A(m_szOwnPhone).get()) { - m_iOwnId = pUser->id_; - - if (!FindUser(pUser->id_)) - m_arUsers.insert(new TG_USER(pUser->id_, 0)); - } - - if (!pUser->is_contact_) { - debugLogA("User doesn't belong to your contacts, skipping"); - return; - } - - auto *pu = AddUser(pUser->id_, false); - UpdateString(pu->hContact, "FirstName", pUser->first_name_); - UpdateString(pu->hContact, "LastName", pUser->last_name_); - UpdateString(pu->hContact, "Phone", pUser->phone_number_); - if (pUser->usernames_) - UpdateString(pu->hContact, "Nick", pUser->usernames_->editable_username_); - - if (pUser->is_premium_) - ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, "tg_premium"); - else - ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, nullptr); - - if (auto *pPhoto = pUser->profile_photo_.get()) { - if (auto *pSmall = pPhoto->small_.get()) { - auto remoteId = pSmall->remote_->unique_id_; - auto storedId = getMStringA(pu->hContact, DBKEY_AVATAR_HASH); - if (remoteId != storedId.c_str()) { - if (!remoteId.empty()) { - pu->szAvatarHash = remoteId.c_str(); - setString(pu->hContact, DBKEY_AVATAR_HASH, remoteId.c_str()); - SendQuery(new TD::downloadFile(pSmall->id_, 5, 0, 0, false)); - } - else delSetting(pu->hContact, DBKEY_AVATAR_HASH); - } - } - } - - if (pUser->status_) { - if (pUser->status_->get_id() == TD::userStatusOffline::ID) { - auto *pOffline = (TD::userStatusOffline *)pUser->status_.get(); - setDword(pu->hContact, "LastSeen", pOffline->was_online_); - } - } -} +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" + +void CMTProto::OnEndSession(td::ClientManager::Response&) +{ + m_bTerminated = true; +} + +void __cdecl CMTProto::ServerThread(void *) +{ + m_bTerminated = m_bAuthorized = false; + + m_pClientMmanager = std::make_unique(); + m_iClientId = m_pClientMmanager->create_client_id(); + + SendQuery(new TD::getOption("version")); + + while (!m_bTerminated) { + ProcessResponse(m_pClientMmanager->receive(1)); + } + + m_pClientMmanager = std::move(nullptr); +} + +void CMTProto::LogOut() +{ + if (m_bTerminated) + return; + + debugLogA("CMTProto::OnLoggedOut"); + m_bTerminated = true; + m_bAuthorized = false; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void CMTProto::OnLoggedIn() +{ + m_bAuthorized = true; + + debugLogA("CMTProto::OnLoggedIn"); + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); + m_iStatus = m_iDesiredStatus; + + if (m_bUnregister) { + SendQuery(new TD::terminateSession()); + SendQuery(new TD::logOut(), &CMTProto::OnEndSession); + } + else SendQuery(new TD::getChats(td::tl::unique_ptr(), 1000)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::SendKeepAlive() +{ + time_t now = time(0); + + for (auto &it : m_arUsers) { + if (it->m_timer1 && now - it->m_timer1 > 600) { + it->m_timer1 = 0; + it->m_timer2 = now; + setWord(it->hContact, "Status", ID_STATUS_AWAY); + } + else if (it->m_timer2 && now - it->m_timer2 > 600) { + it->m_timer2 = 0; + setWord(it->hContact, "Status", ID_STATUS_OFFLINE); + } + } +} + +void CMTProto::SendMarkRead() +{ + m_impl.m_markRead.Stop(); + + mir_cslock lck(m_csMarkRead); + uint64_t userId = _atoi64(getMStringA(m_markContact, DBKEY_ID)); + SendQuery(new TD::viewMessages(userId, 0, std::move(m_markIds), true)); + m_markContact = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessResponse(td::ClientManager::Response response) +{ + if (!response.object) + return; + + debugLogA("ProcessResponse: id=%d (%s)", int(response.request_id), to_string(response.object).c_str()); + + if (response.request_id) { + TG_REQUEST tmp(response.request_id, 0); + auto *p = m_arRequests.find(&tmp); + if (p) { + p->Execute(this, response); + m_arRequests.remove(p); + } + return; + } + + switch (response.object->get_id()) { + case TD::updateAuthorizationState::ID: + ProcessAuth((TD::updateAuthorizationState *)response.object.get()); + break; + + case TD::updateChatFilters::ID: + ProcessGroups((TD::updateChatFilters *)response.object.get()); + break; + + case TD::updateChatPosition::ID: + ProcessChatPosition((TD::updateChatPosition *)response.object.get()); + break; + + case TD::updateChatReadInbox::ID: + ProcessMarkRead((TD::updateChatReadInbox *)response.object.get()); + break; + + case TD::updateFile::ID: + ProcessFile((TD::updateFile*)response.object.get()); + break; + + case TD::updateNewChat::ID: + ProcessChat((TD::updateNewChat *)response.object.get()); + break; + + case TD::updateNewMessage::ID: + ProcessMessage((TD::updateNewMessage *)response.object.get()); + break; + + case TD::updateUserStatus::ID: + ProcessStatus((TD::updateUserStatus *)response.object.get()); + break; + + case TD::updateUser::ID: + ProcessUser((TD::updateUser *)response.object.get()); + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMTProto::OnSendMessage(td::ClientManager::Response &response, void *pUserInfo) +{ + if (!response.object) + return; + + if (response.object->get_id() != TD::message::ID) { + debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::message::ID); + return; + } + + auto *pMessage = ((TD::message *)response.object.get()); + auto *pUser = FindUser(pMessage->chat_id_); + if (pUser) { + char szMsgId[100]; + _i64toa(pMessage->id_, szMsgId, 10); + ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, pUserInfo, (LPARAM)szMsgId); + } +} + +int CMTProto::SendTextMessage(uint64_t chatId, const char *pszMessage) +{ + int ret = m_iMsgId++; + + auto pContent = TD::make_object(); + pContent->text_ = TD::make_object(); + pContent->text_->text_ = std::move(pszMessage); + + auto *pMessage = new TD::sendMessage(); + pMessage->chat_id_ = chatId; + pMessage->input_message_content_ = std::move(pContent); + SendQuery(pMessage, &CMTProto::OnSendMessage, (void*)ret); + + return ret; +} + +void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER pHandler) +{ + int queryId = ++m_iQueryId; + + auto szDescr = to_string(*pFunc); + debugLogA("Sending query %d:\n%s", queryId, szDescr.c_str()); + + m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); + + if (pHandler) + m_arRequests.insert(new TG_REQUEST(queryId, pHandler)); +} + +void CMTProto::SendQuery(TD::Function *pFunc, TG_QUERY_HANDLER_FULL pHandler, void *pUserInfo) +{ + int queryId = ++m_iQueryId; + + auto szDescr = to_string(*pFunc); + debugLogA("Sending full query %d:\n%s", queryId, szDescr.c_str()); + + m_pClientMmanager->send(m_iClientId, queryId, TD::object_ptr(pFunc)); + + if (pHandler) + m_arRequests.insert(new TG_REQUEST_FULL(queryId, pHandler, pUserInfo)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void CMTProto::ProcessChat(TD::updateNewChat *pObj) +{ + auto &pChat = pObj->chat_; + if (pChat->type_->get_id() != TD::chatTypePrivate::ID) { + debugLogA("Only private chats are currently supported"); + return; + } + + if (auto *pUser = FindUser(pChat->id_)) + if (!pChat->title_.empty()) + setUString(pUser->hContact, "Nick", pChat->title_.c_str()); +} + +void CMTProto::ProcessChatPosition(TD::updateChatPosition *pObj) +{ + if (pObj->position_->get_id() != TD::chatPosition::ID) { + debugLogA("Unsupport position"); + return; + } + + auto *pUser = FindUser(pObj->chat_id_); + if (pUser == nullptr) { + debugLogA("Unknown chat, skipping"); + return; + } + + auto *pPos = (TD::chatPosition *)pObj->position_.get(); + if (pPos->list_) { + auto *pList = (TD::chatListFilter*)pPos->list_.get(); + + CMStringA szSetting(FORMAT, "ChatFilter%d", pList->chat_filter_id_); + CMStringW wszGroup(getMStringW(szSetting)); + if (!wszGroup.IsEmpty()) { + ptrW pwszExistingGroup(Clist_GetGroup(pUser->hContact)); + if (!pwszExistingGroup || !mir_wstrcmp(pwszExistingGroup, m_wszDefaultGroup)) { + CMStringW wszNewGroup(FORMAT, L"%s\\%s", (wchar_t *)m_wszDefaultGroup, wszGroup.c_str()); + Clist_SetGroup(pUser->hContact, wszNewGroup); + } + } + } +} + +void CMTProto::ProcessGroups(TD::updateChatFilters *pObj) +{ + for (auto &grp : pObj->chat_filters_) { + if (grp->icon_name_ != "Custom") + continue; + + CMStringA szSetting(FORMAT, "ChatFilter%d", grp->id_); + CMStringW wszOldValue(getMStringW(szSetting)); + Utf2T wszNewValue(grp->title_.c_str()); + if (wszOldValue.IsEmpty()) { + Clist_GroupCreate(m_iBaseGroup, wszNewValue); + setWString(szSetting, wszNewValue); + } + else if (wszOldValue != wszNewValue) { + CMStringW wszFullGroup(FORMAT, L"%s\\%s", (wchar_t*)m_wszDefaultGroup, wszNewValue); + MGROUP oldGroup = Clist_GroupExists(wszFullGroup); + if (!oldGroup) + Clist_GroupCreate(m_iBaseGroup, wszFullGroup); + else + Clist_GroupRename(oldGroup, wszFullGroup); + setWString(szSetting, wszNewValue); + } + } +} + +void CMTProto::ProcessMarkRead(TD::updateChatReadInbox *pObj) +{ + auto *pUser = FindUser(pObj->chat_id_); + if (pUser == nullptr) { + debugLogA("message from unknown chat/user, ignored"); + return; + } + + char szId[100]; + _i64toa(pObj->last_read_inbox_message_id_, szId, 10); + MEVENT hLastRead = db_event_getById(m_szModuleName, szId); + if (hLastRead == 0) { + debugLogA("unknown event, ignored"); + return; + } + + bool bExit = false; + for (MEVENT hEvent = db_event_firstUnread(pUser->hContact); hEvent; hEvent = db_event_next(pUser->hContact, hEvent)) { + if (bExit) + break; + + bExit = (hEvent == hLastRead); + + DBEVENTINFO dbei = {}; + if (db_event_get(hEvent, &dbei)) + continue; + + if (!dbei.markedRead()) + db_event_markRead(pUser->hContact, hEvent); + } +} + +void CMTProto::ProcessMessage(TD::updateNewMessage *pObj) +{ + auto &pMessage = pObj->message_; + + auto *pUser = FindUser(pMessage->chat_id_); + if (pUser == nullptr) { + debugLogA("message from unknown chat/user, ignored"); + return; + } + + if (pUser->isGroupChat) { + debugLogA("message from group chat, ignored"); + return; + } + + CMStringA szText(getMessageText(pMessage->content_.get())); + if (szText.IsEmpty()) { + debugLogA("this message was not processed, ignored"); + return; + } + + char szId[100]; + _i64toa(pMessage->id_, szId, 10); + + PROTORECVEVENT pre = {}; + pre.szMessage = szText.GetBuffer(); + pre.szMsgId = szId; + pre.timestamp = pMessage->date_; + if (pMessage->sender_id_->get_id() == TD::messageSenderUser::ID) + if (((TD::messageSenderUser *)pMessage->sender_id_.get())->user_id_ == m_iOwnId) + pre.flags |= PREF_SENT; + ProtoChainRecvMsg(pUser->hContact, &pre); +} + +void CMTProto::ProcessStatus(TD::updateUserStatus *pObj) +{ + if (auto *pUser = FindUser(pObj->user_id_)) { + if (pObj->status_->get_id() == TD::userStatusOnline::ID) + setWord(pUser->hContact, "Status", ID_STATUS_ONLINE); + else if (pObj->status_->get_id() == TD::userStatusOffline::ID) { + setWord(pUser->hContact, "Status", ID_STATUS_AWAY); + pUser->m_timer1 = time(0); + } + else debugLogA("!!!!! Unknown status packet, report it to the developers"); + } +} + +void CMTProto::ProcessUser(TD::updateUser *pObj) +{ + auto *pUser = pObj->user_.get(); + + if (pUser->phone_number_ == _T2A(m_szOwnPhone).get()) { + m_iOwnId = pUser->id_; + + if (!FindUser(pUser->id_)) + m_arUsers.insert(new TG_USER(pUser->id_, 0)); + } + + if (!pUser->is_contact_) { + debugLogA("User doesn't belong to your contacts, skipping"); + return; + } + + auto *pu = AddUser(pUser->id_, false); + UpdateString(pu->hContact, "FirstName", pUser->first_name_); + UpdateString(pu->hContact, "LastName", pUser->last_name_); + UpdateString(pu->hContact, "Phone", pUser->phone_number_); + if (pUser->usernames_) + UpdateString(pu->hContact, "Nick", pUser->usernames_->editable_username_); + + if (pUser->is_premium_) + ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, "tg_premium"); + else + ExtraIcon_SetIconByName(g_plugin.m_hIcon, pu->hContact, nullptr); + + if (auto *pPhoto = pUser->profile_photo_.get()) { + if (auto *pSmall = pPhoto->small_.get()) { + auto remoteId = pSmall->remote_->unique_id_; + auto storedId = getMStringA(pu->hContact, DBKEY_AVATAR_HASH); + if (remoteId != storedId.c_str()) { + if (!remoteId.empty()) { + pu->szAvatarHash = remoteId.c_str(); + setString(pu->hContact, DBKEY_AVATAR_HASH, remoteId.c_str()); + SendQuery(new TD::downloadFile(pSmall->id_, 5, 0, 0, false)); + } + else delSetting(pu->hContact, DBKEY_AVATAR_HASH); + } + } + } + + if (pUser->status_) { + if (pUser->status_->get_id() == TD::userStatusOffline::ID) { + auto *pOffline = (TD::userStatusOffline *)pUser->status_.get(); + setDword(pu->hContact, "LastSeen", pOffline->was_online_); + } + } +} diff --git a/protocols/Telegram/src/stdafx.cxx b/protocols/Telegram/src/stdafx.cxx index b08670e67f..8c570f6949 100644 --- a/protocols/Telegram/src/stdafx.cxx +++ b/protocols/Telegram/src/stdafx.cxx @@ -1,18 +1,18 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" diff --git a/protocols/Telegram/src/stdafx.h b/protocols/Telegram/src/stdafx.h index cae3629bf9..8e56956840 100644 --- a/protocols/Telegram/src/stdafx.h +++ b/protocols/Telegram/src/stdafx.h @@ -1,48 +1,48 @@ -#ifndef _COMMON_H_ -#define _COMMON_H_ - -#include -#include - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "td/telegram/Client.h" -#include "td/telegram/td_api.h" -#include "td/telegram/td_api.hpp" -namespace TD = td::td_api; - -#define MODULE "Telegram" - -#include "version.h" -#include "resource.h" -#include "mt_proto.h" -#include "utils.h" - -struct CMPlugin : public ACCPROTOPLUGIN -{ - CMPlugin(); - - HANDLE m_hIcon; - - int Load() override; -}; - +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "td/telegram/Client.h" +#include "td/telegram/td_api.h" +#include "td/telegram/td_api.hpp" +namespace TD = td::td_api; + +#define MODULE "Telegram" + +#include "version.h" +#include "resource.h" +#include "mt_proto.h" +#include "utils.h" + +struct CMPlugin : public ACCPROTOPLUGIN +{ + CMPlugin(); + + HANDLE m_hIcon; + + int Load() override; +}; + #endif //_COMMON_H_ \ No newline at end of file diff --git a/protocols/Telegram/src/utils.cpp b/protocols/Telegram/src/utils.cpp index 6113870bd3..df4a0d9118 100644 --- a/protocols/Telegram/src/utils.cpp +++ b/protocols/Telegram/src/utils.cpp @@ -1,126 +1,126 @@ -/* -Copyright (C) 2012-23 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 . -*/ - -#include "stdafx.h" - -void CMTProto::UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str) -{ - if (str.empty()) - delSetting(hContact, pszSetting); - else - setUString(hContact, pszSetting, str.c_str()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Users - -TG_USER* CMTProto::FindUser(uint64_t id) -{ - if (auto *pCache = m_arUsers.find((TG_USER *)&id)) - return pCache; - - return nullptr; -} - -TG_USER* CMTProto::AddUser(uint64_t id, bool bIsChat) -{ - auto *pUser = FindUser(id); - if (pUser != nullptr) - return pUser; - - MCONTACT hContact = db_add_contact(); - Proto_AddToContact(hContact, m_szModuleName); - - char szId[100]; - _i64toa(id, szId, 10); - - if (bIsChat) { - Clist_SetGroup(hContact, TranslateT("Chat rooms")); - setByte(hContact, "ChatRoom", 1); - setString(hContact, "ChatRoomID", szId); - } - else { - setString(hContact, DBKEY_ID, szId); - if (mir_wstrlen(m_wszDefaultGroup)) - Clist_SetGroup(hContact, m_wszDefaultGroup); - } - - pUser = new TG_USER(id, hContact, bIsChat); - m_arUsers.insert(pUser); - return pUser; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popups - -void CMTProto::InitPopups(void) -{ - g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups); - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - wchar_t desc[256]; - mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors")); - - POPUPCLASS ppc = {}; - ppc.flags = PCF_UNICODE; - ppc.pszName = name; - ppc.pszDescription.w = desc; - ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon); - ppc.colorBack = RGB(191, 0, 0); //Red - ppc.colorText = RGB(255, 245, 225); //Yellow - ppc.iSeconds = 60; - m_hPopupClass = Popup_RegisterClass(&ppc); - - IcoLib_ReleaseIcon(ppc.hIcon); -} - -void CMTProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle) -{ - if (!m_bUsePopups) - return; - - char name[256]; - mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); - - CMStringW wszTitle(szTitle); - if (hContact == 0) { - wszTitle.Insert(0, L": "); - wszTitle.Insert(0, m_tszUserName); - } - - POPUPDATACLASS ppd = {}; - ppd.szTitle.w = wszTitle; - ppd.szText.w = szMsg; - ppd.pszClassName = name; - ppd.hContact = hContact; - Popup_AddClass(&ppd); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringA getMessageText(TD::MessageContent *pBody) -{ - if (pBody->get_id() == TD::messageText::ID) { - auto pText = ((TD::messageText *)pBody)->text_.get(); - if (pText->get_id() == TD::formattedText::ID) - return CMStringA(((TD::formattedText *)pText)->text_.c_str()); - } - - return CMStringA(); -} +/* +Copyright (C) 2012-23 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 . +*/ + +#include "stdafx.h" + +void CMTProto::UpdateString(MCONTACT hContact, const char *pszSetting, const std::string &str) +{ + if (str.empty()) + delSetting(hContact, pszSetting); + else + setUString(hContact, pszSetting, str.c_str()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Users + +TG_USER* CMTProto::FindUser(uint64_t id) +{ + if (auto *pCache = m_arUsers.find((TG_USER *)&id)) + return pCache; + + return nullptr; +} + +TG_USER* CMTProto::AddUser(uint64_t id, bool bIsChat) +{ + auto *pUser = FindUser(id); + if (pUser != nullptr) + return pUser; + + MCONTACT hContact = db_add_contact(); + Proto_AddToContact(hContact, m_szModuleName); + + char szId[100]; + _i64toa(id, szId, 10); + + if (bIsChat) { + Clist_SetGroup(hContact, TranslateT("Chat rooms")); + setByte(hContact, "ChatRoom", 1); + setString(hContact, "ChatRoomID", szId); + } + else { + setString(hContact, DBKEY_ID, szId); + if (mir_wstrlen(m_wszDefaultGroup)) + Clist_SetGroup(hContact, m_wszDefaultGroup); + } + + pUser = new TG_USER(id, hContact, bIsChat); + m_arUsers.insert(pUser); + return pUser; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Popups + +void CMTProto::InitPopups(void) +{ + g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups); + + char name[256]; + mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); + + wchar_t desc[256]; + mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors")); + + POPUPCLASS ppc = {}; + ppc.flags = PCF_UNICODE; + ppc.pszName = name; + ppc.pszDescription.w = desc; + ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon); + ppc.colorBack = RGB(191, 0, 0); //Red + ppc.colorText = RGB(255, 245, 225); //Yellow + ppc.iSeconds = 60; + m_hPopupClass = Popup_RegisterClass(&ppc); + + IcoLib_ReleaseIcon(ppc.hIcon); +} + +void CMTProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle) +{ + if (!m_bUsePopups) + return; + + char name[256]; + mir_snprintf(name, "%s_%s", m_szModuleName, "Error"); + + CMStringW wszTitle(szTitle); + if (hContact == 0) { + wszTitle.Insert(0, L": "); + wszTitle.Insert(0, m_tszUserName); + } + + POPUPDATACLASS ppd = {}; + ppd.szTitle.w = wszTitle; + ppd.szText.w = szMsg; + ppd.pszClassName = name; + ppd.hContact = hContact; + Popup_AddClass(&ppd); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CMStringA getMessageText(TD::MessageContent *pBody) +{ + if (pBody->get_id() == TD::messageText::ID) { + auto pText = ((TD::messageText *)pBody)->text_.get(); + if (pText->get_id() == TD::formattedText::ID) + return CMStringA(((TD::formattedText *)pText)->text_.c_str()); + } + + return CMStringA(); +} diff --git a/protocols/Telegram/src/utils.h b/protocols/Telegram/src/utils.h index 2526393a5f..a0811da20e 100644 --- a/protocols/Telegram/src/utils.h +++ b/protocols/Telegram/src/utils.h @@ -1,3 +1,3 @@ -#pragma once - -CMStringA getMessageText(TD::MessageContent *pBody); +#pragma once + +CMStringA getMessageText(TD::MessageContent *pBody); diff --git a/protocols/Telegram/tdlib/tdactor.vcxproj b/protocols/Telegram/tdlib/tdactor.vcxproj index b8023c00e1..dfce57bbe9 100644 --- a/protocols/Telegram/tdlib/tdactor.vcxproj +++ b/protocols/Telegram/tdlib/tdactor.vcxproj @@ -1,75 +1,75 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {85F63934-02FE-332A-8703-059040B65512} - tdactor - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - .\td\tdactor;.\td\tdutils;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {85F63934-02FE-332A-8703-059040B65512} + tdactor + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + .\td\tdactor;.\td\tdutils;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdactor.vcxproj.filters b/protocols/Telegram/tdlib/tdactor.vcxproj.filters index 341d789cce..5535137d6d 100644 --- a/protocols/Telegram/tdlib/tdactor.vcxproj.filters +++ b/protocols/Telegram/tdlib/tdactor.vcxproj.filters @@ -1,60 +1,60 @@ - - - - - - - - Impl - - - - - - - - - - - - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - Impl - - - - - {781720b8-b4b7-4498-9e33-b123d317aea3} - - + + + + + + + + Impl + + + + + + + + + + + + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + Impl + + + + + {781720b8-b4b7-4498-9e33-b123d317aea3} + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdcore.vcxproj b/protocols/Telegram/tdlib/tdcore.vcxproj index 1c9cb96322..238d8a5bee 100644 --- a/protocols/Telegram/tdlib/tdcore.vcxproj +++ b/protocols/Telegram/tdlib/tdcore.vcxproj @@ -1,299 +1,299 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {FC88FB5A-AAED-3F3E-9959-236444D8F644} - tdcore - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - .\td;.\td\td\generate\auto;..\..\..\include;.\td\tdactor;.\td\tdutils;.\td\tdnet;.\td\tddb;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {85F63934-02FE-332A-8703-059040B65512} - tdactor - - - {F525EE11-8820-3D8A-87A5-465D50A98A64} - tddb - - - {2246C3CF-7888-3102-984A-80214ADF418C} - tdnet - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FC88FB5A-AAED-3F3E-9959-236444D8F644} + tdcore + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + .\td;.\td\td\generate\auto;..\..\..\include;.\td\tdactor;.\td\tdutils;.\td\tdnet;.\td\tddb;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {85F63934-02FE-332A-8703-059040B65512} + tdactor + + + {F525EE11-8820-3D8A-87A5-465D50A98A64} + tddb + + + {2246C3CF-7888-3102-984A-80214ADF418C} + tdnet + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdcore.vcxproj.filters b/protocols/Telegram/tdlib/tdcore.vcxproj.filters index 4a41579e0d..155ff0b5ff 100644 --- a/protocols/Telegram/tdlib/tdcore.vcxproj.filters +++ b/protocols/Telegram/tdlib/tdcore.vcxproj.filters @@ -1,251 +1,251 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {0FD26E20-5E51-396B-B4E5-98068F96B37E} - - - {CC4593AA-1CC3-37C8-BDF9-C5986B1808BD} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {0FD26E20-5E51-396B-B4E5-98068F96B37E} + + + {CC4593AA-1CC3-37C8-BDF9-C5986B1808BD} + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tddb.vcxproj b/protocols/Telegram/tdlib/tddb.vcxproj index cb17625164..34806361c6 100644 --- a/protocols/Telegram/tdlib/tddb.vcxproj +++ b/protocols/Telegram/tdlib/tddb.vcxproj @@ -1,88 +1,88 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {F525EE11-8820-3D8A-87A5-465D50A98A64} - tddb - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - .\td\tddb;.\td\tdactor;.\td\tdutils;.\td\build\tdutils;.\td\sqlite;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {85F63934-02FE-332A-8703-059040B65512} - tdactor - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F525EE11-8820-3D8A-87A5-465D50A98A64} + tddb + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + .\td\tddb;.\td\tdactor;.\td\tdutils;.\td\build\tdutils;.\td\sqlite;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {85F63934-02FE-332A-8703-059040B65512} + tdactor + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tddb.vcxproj.filters b/protocols/Telegram/tdlib/tddb.vcxproj.filters index cf931303d3..bd37df119b 100644 --- a/protocols/Telegram/tdlib/tddb.vcxproj.filters +++ b/protocols/Telegram/tdlib/tddb.vcxproj.filters @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdlib.vcxproj b/protocols/Telegram/tdlib/tdlib.vcxproj index 14129c6555..18d50db4b4 100644 --- a/protocols/Telegram/tdlib/tdlib.vcxproj +++ b/protocols/Telegram/tdlib/tdlib.vcxproj @@ -1,607 +1,607 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {800E305A-3704-4617-ADA0-DEE8EAFDB804} - tdlib - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - td;td\td\generate\auto;td\tdactor;td\tdutils;td\tdnet;td\tddb;..\..\..\include;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {85F63934-02FE-332A-8703-059040B65512} - tdactor - - - {F525EE11-8820-3D8A-87A5-465D50A98A64} - tddb - - - {2246C3CF-7888-3102-984A-80214ADF418C} - tdnet - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {800E305A-3704-4617-ADA0-DEE8EAFDB804} + tdlib + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + td;td\td\generate\auto;td\tdactor;td\tdutils;td\tdnet;td\tddb;..\..\..\include;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {85F63934-02FE-332A-8703-059040B65512} + tdactor + + + {F525EE11-8820-3D8A-87A5-465D50A98A64} + tddb + + + {2246C3CF-7888-3102-984A-80214ADF418C} + tdnet + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdlib.vcxproj.filters b/protocols/Telegram/tdlib/tdlib.vcxproj.filters index ee0c097760..09f9ca4b73 100644 --- a/protocols/Telegram/tdlib/tdlib.vcxproj.filters +++ b/protocols/Telegram/tdlib/tdlib.vcxproj.filters @@ -1,1656 +1,1656 @@ - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\logevent - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\net - - - Source Files\logevent - - - Source Files\logevent - - - Source Files\logevent - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Source Files\files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - {0FD26E20-5E51-396B-B4E5-98068F96B37E} - - - {CC4593AA-1CC3-37C8-BDF9-C5986B1808BD} - - - {cd939197-1880-4e6c-8611-971cb070e878} - - - {c2c002f7-0b68-47b7-886e-03a433ca7017} - - - {514d7959-632a-4de8-8df3-0eeeadaaacbf} - - + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\logevent + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\net + + + Source Files\logevent + + + Source Files\logevent + + + Source Files\logevent + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Source Files\files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {0FD26E20-5E51-396B-B4E5-98068F96B37E} + + + {CC4593AA-1CC3-37C8-BDF9-C5986B1808BD} + + + {cd939197-1880-4e6c-8611-971cb070e878} + + + {c2c002f7-0b68-47b7-886e-03a433ca7017} + + + {514d7959-632a-4de8-8df3-0eeeadaaacbf} + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdnet.vcxproj b/protocols/Telegram/tdlib/tdnet.vcxproj index ba945968c0..9a84f80874 100644 --- a/protocols/Telegram/tdlib/tdnet.vcxproj +++ b/protocols/Telegram/tdlib/tdnet.vcxproj @@ -1,89 +1,89 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {2246C3CF-7888-3102-984A-80214ADF418C} - tdnet - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - .\td\tdnet;..\..\..\include;.\td\tdutils;.\td\tdactor;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {85F63934-02FE-332A-8703-059040B65512} - tdactor - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2246C3CF-7888-3102-984A-80214ADF418C} + tdnet + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + .\td\tdnet;..\..\..\include;.\td\tdutils;.\td\tdactor;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {85F63934-02FE-332A-8703-059040B65512} + tdactor + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdnet.vcxproj.filters b/protocols/Telegram/tdlib/tdnet.vcxproj.filters index 87decdbf04..1ea9fed8c4 100644 --- a/protocols/Telegram/tdlib/tdnet.vcxproj.filters +++ b/protocols/Telegram/tdlib/tdnet.vcxproj.filters @@ -1,41 +1,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdutils.vcxproj b/protocols/Telegram/tdlib/tdutils.vcxproj index db5abf5030..4db92aa0fb 100644 --- a/protocols/Telegram/tdlib/tdutils.vcxproj +++ b/protocols/Telegram/tdlib/tdutils.vcxproj @@ -1,293 +1,293 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} - tdutils - - - v141_xp - - - - $(ProjectDir)lib\$(Configuration)32\ - $(ProjectDir)lib\$(Configuration)64\ - - - - .\td\tdutils;..\..\..\include;..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories) - %(AdditionalOptions) /bigobj - 4100;4127;4324;4505;4702 - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D21C6A0F-BED1-3377-9659-7FC7D82EFC4F} + tdutils + + + v141_xp + + + + $(ProjectDir)lib\$(Configuration)32\ + $(ProjectDir)lib\$(Configuration)64\ + + + + .\td\tdutils;..\..\..\include;..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories) + %(AdditionalOptions) /bigobj + 4100;4127;4324;4505;4702 + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Telegram/tdlib/tdutils.vcxproj.filters b/protocols/Telegram/tdlib/tdutils.vcxproj.filters index 5aa66dd4d0..c7b27bd63d 100644 --- a/protocols/Telegram/tdlib/tdutils.vcxproj.filters +++ b/protocols/Telegram/tdlib/tdutils.vcxproj.filters @@ -1,255 +1,255 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/WhatsApp/src/appsync.cpp b/protocols/WhatsApp/src/appsync.cpp index d298f880d2..9607045131 100644 --- a/protocols/WhatsApp/src/appsync.cpp +++ b/protocols/WhatsApp/src/appsync.cpp @@ -1,322 +1,322 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-23 George Hazan - -*/ - -#include "stdafx.h" - -////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::InitSync() -{ - m_arCollections.insert(new WACollection("regular")); - m_arCollections.insert(new WACollection("regular_high")); - m_arCollections.insert(new WACollection("regular_low")); - m_arCollections.insert(new WACollection("critical_block")); - m_arCollections.insert(new WACollection("critical_unblock_low")); - - for (auto &it : m_arCollections) { - CMStringW wszPath(GetTmpFileName("collection", it->szName)); - wszPath.Append(L".json"); - if (_waccess(wszPath, 0)) - continue; - - JSONNode root = JSONNode::parse(file2string(wszPath)); - it->version = root["version"].as_int(); - - auto szHash = decodeBinStr(root["hash"].as_string()); - if (szHash.size() == sizeof(it->hash.hash)) - memcpy(it->hash.hash, szHash.c_str(), sizeof(it->hash.hash)); - - for (auto &val : root["indexValueMap"]) - it->indexValueMap[decodeBinStr(val.name())] = decodeBinStr(val.as_string()); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnServerSync(const WANode &node) -{ - OBJLIST task(1); - - for (auto &it : node.getChildren()) - if (it->title == "collection") - task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version"))); - - ResyncServer(task); - SendAck(node); -} - -void WhatsAppProto::ResyncAll() -{ - ResyncServer(m_arCollections); -} - -void WhatsAppProto::ResyncServer(const OBJLIST &task) -{ - WANodeIq iq(IQ::SET, "w:sync:app:state"); - - auto *pList = iq.addChild("sync"); - for (auto &it : task) { - auto *pCollection = m_arCollections.find(it); - if (pCollection == nullptr) - m_arCollections.insert(pCollection = new WACollection(it->szName, 0)); - - if (!pCollection->version || pCollection->version < it->version) { - auto *pNode = pList->addChild("collection"); - *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version) - << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false"); - } - } - - if (pList->getFirstChild() != nullptr) - WSSendNode(iq, &WhatsAppProto::OnIqServerSync); -} - -void WhatsAppProto::OnIqServerSync(const WANode &node) -{ - for (auto &coll : node.getChild("sync")->getChildren()) { - if (coll->title != "collection") - continue; - - auto *pszName = coll->getAttr("name"); - - auto *pCollection = FindCollection(pszName); - if (pCollection == nullptr) { - pCollection = new WACollection(pszName, 0); - m_arCollections.insert(pCollection); - } - - int dwVersion = 0; - - CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName)); - if (auto *pSnapshot = coll->getChild("snapshot")) { - proto::ExternalBlobReference body(pSnapshot->content); - if (!body->directpath || !body->has_mediakey) { - debugLogA("Invalid snapshot data, skipping"); - continue; - } - - MBinBuffer buf = DownloadEncryptedFile(directPath2url(body->directpath), body->mediakey, "App State"); - if (buf.isEmpty()) { - debugLogA("Invalid downloaded snapshot data, skipping"); - continue; - } - - proto::SyncdSnapshot snapshot(unpadBuffer16(buf)); - if (!snapshot) { - debugLogA("%s: unable to decode snapshot, skipping"); - continue; - } - - dwVersion = snapshot->version->version; - if (dwVersion > pCollection->version) { - pCollection->hash.init(); - debugLogA("%s: applying snapshot of version %d", pCollection->szName.get(), dwVersion); - for (int i=0; i < snapshot->n_records; i++) - ParsePatch(pCollection, snapshot->records[i], true); - } - else debugLogA("%s: skipping snapshot of version %d", pCollection->szName.get(), dwVersion); - } - - if (auto *pPatchList = coll->getChild("patches")) { - for (auto &it : pPatchList->getChildren()) { - proto::SyncdPatch patch(it->content); - if (!patch) { - debugLogA("%s: unable to decode patch, skipping"); - continue; - } - - dwVersion = patch->version->version; - if (dwVersion > pCollection->version) { - debugLogA("%s: applying patch of version %d", pCollection->szName.get(), dwVersion); - for (int i = 0; i < patch->n_mutations; i++) { - auto &jt = *patch->mutations[i]; - ParsePatch(pCollection, jt.record, jt.operation == WA__SYNCD_MUTATION__SYNCD_OPERATION__SET); - } - } - else debugLogA("%s: skipping patch of version %d", pCollection->szName.get(), dwVersion); - } - } - - JSONNode jsonRoot, jsonMap; - for (auto &it : pCollection->indexValueMap) - jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size()))); - jsonRoot << INT_PARAM("version", dwVersion) << CHAR_PARAM("hash", ptrA(mir_base64_encode(pCollection->hash.hash, sizeof(pCollection->hash.hash)))) - << JSON_PARAM("indexValueMap", jsonMap); - - string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json")); - } -} - -static uint8_t sttMutationInfo[] = "WhatsApp Mutation Keys"; - -void WhatsAppProto::ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet) -{ - int id = decodeBigEndian(rec->keyid->id); - auto &indexBlob = rec->index->blob; - auto &value = rec->value->blob; - - auto *macValue = value.data + value.len - 32; - std::string index((char *)indexBlob.data, indexBlob.len); - - MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id))); - if (key.isEmpty()) { - debugLogA("No key with id=%d to decode a patch"); - return; - } - - struct - { - uint8_t indexKey[32]; - uint8_t encKey[32]; - uint8_t macKey[32]; - uint8_t snapshotMacKey[32]; - uint8_t patchMacKey[32]; - - } mutationKeys; - - HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE *)&mutationKeys, sizeof(mutationKeys)); - - MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, value.data, value.data + 16, value.len - 32); - if (decoded.isEmpty()) { - debugLogA("Unable to decode patch with key id=%d", id); - return; - } - - proto::SyncActionData data(unpadBuffer16(decoded)); - if (!data) { - debugLogA("Unable to decode action data with id=%d", id); - return; - } - - JSONNode jsonRoot = JSONNode::parse((char *)data->index.data); - - if (bSet) { - ApplyPatch(jsonRoot, data->value); - - pColl->hash.add(macValue, 32); - pColl->indexValueMap[index] = std::string((char*)macValue, 32); - } - else { - debugLogA("Removing data with index: %s", jsonRoot.write().c_str()); - - auto &prevVal = pColl->indexValueMap.find(index); - if (prevVal != pColl->indexValueMap.end()) { - pColl->hash.sub(prevVal->second.c_str(), prevVal->second.size()); - pColl->indexValueMap.erase(prevVal); - } - } -} - -void WhatsAppProto::ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data) -{ - debugLogA("Applying patch for %s: %s", index.write().c_str(), protobuf_c_text_to_string(data).c_str()); - - auto title = index.at((json_index_t)0).as_string(); - - if (title == "contact" && data->contactaction) { - auto *pUser = AddUser(index.at(1).as_string().c_str(), false); - - auto *pAction = data->contactaction; - auto &fullName = pAction->fullname; - if (fullName) - setUString(pUser->hContact, "Nick", fullName); - - if (pAction->firstname) { - CMStringA str(pAction->firstname); - str.TrimRight(); - setUString(pUser->hContact, "FirstName", str.c_str()); - setUString(pUser->hContact, "LastName", fullName + str.GetLength() + 1); - } - else if (fullName != nullptr) { - auto *p = strrchr(fullName, ' '); - if (p != 0) { - *p = 0; - setUString(pUser->hContact, "FirstName", fullName); - setUString(pUser->hContact, "LastName", p+1); - } - else { - setUString(pUser->hContact, "FirstName", ""); - setUString(pUser->hContact, "LastName", fullName); - } - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ProcessHistorySync(const Wa__HistorySync *pSync) -{ - debugLogA("Got history sync: %s", protobuf_c_text_to_string(pSync).c_str()); - - switch (pSync->synctype) { - case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP: - case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__RECENT: - for (int i = 0; i < pSync->n_conversations; i++) { - auto *pChat = pSync->conversations[i]; - - auto *pUser = AddUser(pChat->id, false); - for (int j = 0; j < pChat->n_messages; j++) { - auto *pMessage = pChat->messages[j]; - if (!pMessage->message) - continue; - - MEVENT hEvent = db_event_getById(m_szModuleName, pMessage->message->key->id); - if (hEvent) { - debugLogA("Event %s is already processed", pMessage->message->key->id); - continue; - } - - CMStringA szMessageText(GetMessageText(pMessage->message->message)); - if (!szMessageText.IsEmpty()) { - auto *key = pMessage->message->key; - - PROTORECVEVENT pre = {}; - pre.timestamp = pMessage->message->messagetimestamp; - pre.szMessage = szMessageText.GetBuffer(); - pre.szMsgId = key->id; - pre.flags = PREF_CREATEREAD; - if (key->fromme) - pre.flags |= PREF_SENT; - ProtoChainRecvMsg(pUser->hContact, &pre); - - if (pUser->bIsGroupChat) { - if (pChat->name) - setUString(pUser->hContact, "Nick", pChat->name); - - GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE}; - gce.dwFlags = GCEF_UTF8; - gce.pszID.a = pUser->szId; - gce.pszUID.a = key->participant; - gce.bIsMe = key->fromme; - gce.pszText.a = szMessageText.GetBuffer(); - gce.time = pMessage->message->messagetimestamp; - Chat_Event(&gce); - } - } - } - } - - if (pSync->synctype == WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP) - GC_RefreshMetadata(); - break; - - case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__PUSH_NAME: - for (int i = 0; i < pSync->n_pushnames; i++) { - auto *pName = pSync->pushnames[i]; - if (auto *pUser = AddUser(pName->id, false)) - setUString(pUser->hContact, "Nick", pName->pushname); - } - break; - - case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_STATUS_V3: - for (int i = 0; i < pSync->n_statusv3messages; i++) { - // TODO - // auto *pStatus = pSync->statusv3messages[i]; - } - break; - } -} +/* + +WhatsApp plugin for Miranda NG +Copyright © 2019-23 George Hazan + +*/ + +#include "stdafx.h" + +////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::InitSync() +{ + m_arCollections.insert(new WACollection("regular")); + m_arCollections.insert(new WACollection("regular_high")); + m_arCollections.insert(new WACollection("regular_low")); + m_arCollections.insert(new WACollection("critical_block")); + m_arCollections.insert(new WACollection("critical_unblock_low")); + + for (auto &it : m_arCollections) { + CMStringW wszPath(GetTmpFileName("collection", it->szName)); + wszPath.Append(L".json"); + if (_waccess(wszPath, 0)) + continue; + + JSONNode root = JSONNode::parse(file2string(wszPath)); + it->version = root["version"].as_int(); + + auto szHash = decodeBinStr(root["hash"].as_string()); + if (szHash.size() == sizeof(it->hash.hash)) + memcpy(it->hash.hash, szHash.c_str(), sizeof(it->hash.hash)); + + for (auto &val : root["indexValueMap"]) + it->indexValueMap[decodeBinStr(val.name())] = decodeBinStr(val.as_string()); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnServerSync(const WANode &node) +{ + OBJLIST task(1); + + for (auto &it : node.getChildren()) + if (it->title == "collection") + task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version"))); + + ResyncServer(task); + SendAck(node); +} + +void WhatsAppProto::ResyncAll() +{ + ResyncServer(m_arCollections); +} + +void WhatsAppProto::ResyncServer(const OBJLIST &task) +{ + WANodeIq iq(IQ::SET, "w:sync:app:state"); + + auto *pList = iq.addChild("sync"); + for (auto &it : task) { + auto *pCollection = m_arCollections.find(it); + if (pCollection == nullptr) + m_arCollections.insert(pCollection = new WACollection(it->szName, 0)); + + if (!pCollection->version || pCollection->version < it->version) { + auto *pNode = pList->addChild("collection"); + *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version) + << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false"); + } + } + + if (pList->getFirstChild() != nullptr) + WSSendNode(iq, &WhatsAppProto::OnIqServerSync); +} + +void WhatsAppProto::OnIqServerSync(const WANode &node) +{ + for (auto &coll : node.getChild("sync")->getChildren()) { + if (coll->title != "collection") + continue; + + auto *pszName = coll->getAttr("name"); + + auto *pCollection = FindCollection(pszName); + if (pCollection == nullptr) { + pCollection = new WACollection(pszName, 0); + m_arCollections.insert(pCollection); + } + + int dwVersion = 0; + + CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName)); + if (auto *pSnapshot = coll->getChild("snapshot")) { + proto::ExternalBlobReference body(pSnapshot->content); + if (!body->directpath || !body->has_mediakey) { + debugLogA("Invalid snapshot data, skipping"); + continue; + } + + MBinBuffer buf = DownloadEncryptedFile(directPath2url(body->directpath), body->mediakey, "App State"); + if (buf.isEmpty()) { + debugLogA("Invalid downloaded snapshot data, skipping"); + continue; + } + + proto::SyncdSnapshot snapshot(unpadBuffer16(buf)); + if (!snapshot) { + debugLogA("%s: unable to decode snapshot, skipping"); + continue; + } + + dwVersion = snapshot->version->version; + if (dwVersion > pCollection->version) { + pCollection->hash.init(); + debugLogA("%s: applying snapshot of version %d", pCollection->szName.get(), dwVersion); + for (int i=0; i < snapshot->n_records; i++) + ParsePatch(pCollection, snapshot->records[i], true); + } + else debugLogA("%s: skipping snapshot of version %d", pCollection->szName.get(), dwVersion); + } + + if (auto *pPatchList = coll->getChild("patches")) { + for (auto &it : pPatchList->getChildren()) { + proto::SyncdPatch patch(it->content); + if (!patch) { + debugLogA("%s: unable to decode patch, skipping"); + continue; + } + + dwVersion = patch->version->version; + if (dwVersion > pCollection->version) { + debugLogA("%s: applying patch of version %d", pCollection->szName.get(), dwVersion); + for (int i = 0; i < patch->n_mutations; i++) { + auto &jt = *patch->mutations[i]; + ParsePatch(pCollection, jt.record, jt.operation == WA__SYNCD_MUTATION__SYNCD_OPERATION__SET); + } + } + else debugLogA("%s: skipping patch of version %d", pCollection->szName.get(), dwVersion); + } + } + + JSONNode jsonRoot, jsonMap; + for (auto &it : pCollection->indexValueMap) + jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size()))); + jsonRoot << INT_PARAM("version", dwVersion) << CHAR_PARAM("hash", ptrA(mir_base64_encode(pCollection->hash.hash, sizeof(pCollection->hash.hash)))) + << JSON_PARAM("indexValueMap", jsonMap); + + string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json")); + } +} + +static uint8_t sttMutationInfo[] = "WhatsApp Mutation Keys"; + +void WhatsAppProto::ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet) +{ + int id = decodeBigEndian(rec->keyid->id); + auto &indexBlob = rec->index->blob; + auto &value = rec->value->blob; + + auto *macValue = value.data + value.len - 32; + std::string index((char *)indexBlob.data, indexBlob.len); + + MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id))); + if (key.isEmpty()) { + debugLogA("No key with id=%d to decode a patch"); + return; + } + + struct + { + uint8_t indexKey[32]; + uint8_t encKey[32]; + uint8_t macKey[32]; + uint8_t snapshotMacKey[32]; + uint8_t patchMacKey[32]; + + } mutationKeys; + + HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE *)&mutationKeys, sizeof(mutationKeys)); + + MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, value.data, value.data + 16, value.len - 32); + if (decoded.isEmpty()) { + debugLogA("Unable to decode patch with key id=%d", id); + return; + } + + proto::SyncActionData data(unpadBuffer16(decoded)); + if (!data) { + debugLogA("Unable to decode action data with id=%d", id); + return; + } + + JSONNode jsonRoot = JSONNode::parse((char *)data->index.data); + + if (bSet) { + ApplyPatch(jsonRoot, data->value); + + pColl->hash.add(macValue, 32); + pColl->indexValueMap[index] = std::string((char*)macValue, 32); + } + else { + debugLogA("Removing data with index: %s", jsonRoot.write().c_str()); + + auto &prevVal = pColl->indexValueMap.find(index); + if (prevVal != pColl->indexValueMap.end()) { + pColl->hash.sub(prevVal->second.c_str(), prevVal->second.size()); + pColl->indexValueMap.erase(prevVal); + } + } +} + +void WhatsAppProto::ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data) +{ + debugLogA("Applying patch for %s: %s", index.write().c_str(), protobuf_c_text_to_string(data).c_str()); + + auto title = index.at((json_index_t)0).as_string(); + + if (title == "contact" && data->contactaction) { + auto *pUser = AddUser(index.at(1).as_string().c_str(), false); + + auto *pAction = data->contactaction; + auto &fullName = pAction->fullname; + if (fullName) + setUString(pUser->hContact, "Nick", fullName); + + if (pAction->firstname) { + CMStringA str(pAction->firstname); + str.TrimRight(); + setUString(pUser->hContact, "FirstName", str.c_str()); + setUString(pUser->hContact, "LastName", fullName + str.GetLength() + 1); + } + else if (fullName != nullptr) { + auto *p = strrchr(fullName, ' '); + if (p != 0) { + *p = 0; + setUString(pUser->hContact, "FirstName", fullName); + setUString(pUser->hContact, "LastName", p+1); + } + else { + setUString(pUser->hContact, "FirstName", ""); + setUString(pUser->hContact, "LastName", fullName); + } + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::ProcessHistorySync(const Wa__HistorySync *pSync) +{ + debugLogA("Got history sync: %s", protobuf_c_text_to_string(pSync).c_str()); + + switch (pSync->synctype) { + case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP: + case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__RECENT: + for (int i = 0; i < pSync->n_conversations; i++) { + auto *pChat = pSync->conversations[i]; + + auto *pUser = AddUser(pChat->id, false); + for (int j = 0; j < pChat->n_messages; j++) { + auto *pMessage = pChat->messages[j]; + if (!pMessage->message) + continue; + + MEVENT hEvent = db_event_getById(m_szModuleName, pMessage->message->key->id); + if (hEvent) { + debugLogA("Event %s is already processed", pMessage->message->key->id); + continue; + } + + CMStringA szMessageText(GetMessageText(pMessage->message->message)); + if (!szMessageText.IsEmpty()) { + auto *key = pMessage->message->key; + + PROTORECVEVENT pre = {}; + pre.timestamp = pMessage->message->messagetimestamp; + pre.szMessage = szMessageText.GetBuffer(); + pre.szMsgId = key->id; + pre.flags = PREF_CREATEREAD; + if (key->fromme) + pre.flags |= PREF_SENT; + ProtoChainRecvMsg(pUser->hContact, &pre); + + if (pUser->bIsGroupChat) { + if (pChat->name) + setUString(pUser->hContact, "Nick", pChat->name); + + GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE}; + gce.dwFlags = GCEF_UTF8; + gce.pszID.a = pUser->szId; + gce.pszUID.a = key->participant; + gce.bIsMe = key->fromme; + gce.pszText.a = szMessageText.GetBuffer(); + gce.time = pMessage->message->messagetimestamp; + Chat_Event(&gce); + } + } + } + } + + if (pSync->synctype == WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP) + GC_RefreshMetadata(); + break; + + case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__PUSH_NAME: + for (int i = 0; i < pSync->n_pushnames; i++) { + auto *pName = pSync->pushnames[i]; + if (auto *pUser = AddUser(pName->id, false)) + setUString(pUser->hContact, "Nick", pName->pushname); + } + break; + + case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_STATUS_V3: + for (int i = 0; i < pSync->n_statusv3messages; i++) { + // TODO + // auto *pStatus = pSync->statusv3messages[i]; + } + break; + } +} diff --git a/protocols/WhatsApp/src/chats.cpp b/protocols/WhatsApp/src/chats.cpp index 59f98d4548..b0423e5b20 100644 --- a/protocols/WhatsApp/src/chats.cpp +++ b/protocols/WhatsApp/src/chats.cpp @@ -1,188 +1,188 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-23 George Hazan - -*/ - -#include "stdafx.h" - -void WhatsAppProto::GC_RefreshMetadata() -{ - for (auto &it : m_arUsers) { - if (it->bIsGroupChat) { - GC_GetAllMetadata(); - break; - } - } -} - -void WhatsAppProto::GC_GetAllMetadata() -{ - WANodeIq iq(IQ::GET, "w:g2", "@g.us"); - auto *pRoot = iq.addChild("participating"); - *pRoot << XCHILD("participants") << XCHILD("description"); - WSSendNode(iq, &WhatsAppProto::OnIqGcGetAllMetadata); -} - -void WhatsAppProto::OnIqGcGetAllMetadata(const WANode &node) -{ - if (auto *pGroup = node.getChild("groups")) - for (auto &it : pGroup->getChildren()) - GC_ParseMetadata(it); -} - -void WhatsAppProto::GC_ParseMetadata(const WANode *pGroup) -{ - auto *pszId = pGroup->getAttr("id"); - if (pszId == nullptr) - return; - - auto *pChatUser = AddUser(CMStringA(pszId) + "@g.us", false); - if (pChatUser == nullptr) - return; - - CMStringW wszId(Utf2T(pChatUser->szId)); - - pChatUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pChatUser->hContact, "Nick")); - - Chat_AddGroup(pChatUser->si, TranslateT("Owner")); - Chat_AddGroup(pChatUser->si, TranslateT("SuperAdmin")); - Chat_AddGroup(pChatUser->si, TranslateT("Admin")); - Chat_AddGroup(pChatUser->si, TranslateT("Participant")); - - CMStringA szOwner(pGroup->getAttr("creator")), szNick, szRole; - - for (auto &it : pGroup->getChildren()) { - if (it->title == "description") { - CMStringA szDescr = it->getBody(); - if (!szDescr.IsEmpty()) { - GCEVENT gce = {m_szModuleName, 0, GC_EVENT_INFORMATION}; - gce.dwFlags = GCEF_UTF8; - gce.pszID.a = pChatUser->szId; - gce.pszText.a = szDescr.c_str(); - Chat_Event(&gce); - } - } - else if (it->title == "member_add_mode") { - szRole = it->getBody(); - } - else if (it->title == "participant") { - auto *jid = it->getAttr("jid"); - - // if role isn't specified, use the default one - auto *role = it->getAttr("type"); - if (role == nullptr) - role = szRole; - - GCEVENT gce = {m_szModuleName, 0, GC_EVENT_JOIN}; - gce.dwFlags = GCEF_UTF8; - gce.pszID.a = pChatUser->szId; - gce.pszUID.a = jid; - gce.bIsMe = (jid == m_szJid); - - if (jid == szOwner) - gce.pszStatus.a = "Owner"; - else if (!mir_strcmp(role, "superadmin")) - gce.pszStatus.a = "SuperAdmin"; - else if (!mir_strcmp(role, "adminadd")) - gce.pszStatus.a = "Admin"; - else - gce.pszStatus.a = "Participant"; - - if (gce.bIsMe) - szNick = ptrA(getUStringA(DBKEY_NICK)); - else if (auto *pUser = FindUser(jid)) - szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get(); - else - szNick = WAJid(jid).user; - - gce.pszNick.a = szNick; - Chat_Event(&gce); - } - } - - if (auto *pszSubject = pGroup->getAttr("subject")) { - time_t iSubjectTime = pGroup->getAttrInt("s_t"); - auto *pszUser = pGroup->getAttr("s_o"); - if (m_szJid == pszUser) - szNick = ptrA(getUStringA(DBKEY_NICK)); - else if (auto *pUser = FindUser(pszUser)) - szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get(); - else - szNick = WAJid(pszUser).user; - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; - gce.dwFlags = GCEF_UTF8; - gce.pszID.a = pChatUser->szId; - gce.pszUID.a = pszUser; - gce.pszText.a = pszSubject; - gce.time = iSubjectTime; - Chat_Event(&gce); - - setUString(pChatUser->hContact, "Nick", pszSubject); - } - - pChatUser->bInited = true; - Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); - Chat_Control(m_szModuleName, wszId, SESSION_ONLINE); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int WhatsAppProto::GcEventHook(WPARAM, LPARAM lParam) -{ - GCHOOK *gch = (GCHOOK*)lParam; - if (gch == nullptr) - return 0; - - if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) - return 0; - - auto *pUser = FindUser(T2Utf(gch->si->ptszID)); - if (pUser == nullptr) - return 0; - - switch (gch->iType) { - case GC_USER_MESSAGE: - if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) { - rtrimw(gch->ptszText); - Chat_UnescapeTags(gch->ptszText); - SendTextMessage(pUser->szId, T2Utf(gch->ptszText)); - } - break; - - case GC_USER_PRIVMESS: - break; - - case GC_USER_LOGMENU: - break; - - case GC_USER_NICKLISTMENU: - break; - } - - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int WhatsAppProto::GcMenuHook(WPARAM, LPARAM lParam) -{ - GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; - if (gcmi == nullptr) - return 0; - - if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) - return 0; - - auto *pUser = FindUser(T2Utf(gcmi->pszID)); - if (pUser == nullptr) - return 0; - - if (gcmi->Type == MENU_ON_LOG) { - } - else if (gcmi->Type == MENU_ON_NICKLIST) { - } - return 0; -} +/* + +WhatsApp plugin for Miranda NG +Copyright © 2019-23 George Hazan + +*/ + +#include "stdafx.h" + +void WhatsAppProto::GC_RefreshMetadata() +{ + for (auto &it : m_arUsers) { + if (it->bIsGroupChat) { + GC_GetAllMetadata(); + break; + } + } +} + +void WhatsAppProto::GC_GetAllMetadata() +{ + WANodeIq iq(IQ::GET, "w:g2", "@g.us"); + auto *pRoot = iq.addChild("participating"); + *pRoot << XCHILD("participants") << XCHILD("description"); + WSSendNode(iq, &WhatsAppProto::OnIqGcGetAllMetadata); +} + +void WhatsAppProto::OnIqGcGetAllMetadata(const WANode &node) +{ + if (auto *pGroup = node.getChild("groups")) + for (auto &it : pGroup->getChildren()) + GC_ParseMetadata(it); +} + +void WhatsAppProto::GC_ParseMetadata(const WANode *pGroup) +{ + auto *pszId = pGroup->getAttr("id"); + if (pszId == nullptr) + return; + + auto *pChatUser = AddUser(CMStringA(pszId) + "@g.us", false); + if (pChatUser == nullptr) + return; + + CMStringW wszId(Utf2T(pChatUser->szId)); + + pChatUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pChatUser->hContact, "Nick")); + + Chat_AddGroup(pChatUser->si, TranslateT("Owner")); + Chat_AddGroup(pChatUser->si, TranslateT("SuperAdmin")); + Chat_AddGroup(pChatUser->si, TranslateT("Admin")); + Chat_AddGroup(pChatUser->si, TranslateT("Participant")); + + CMStringA szOwner(pGroup->getAttr("creator")), szNick, szRole; + + for (auto &it : pGroup->getChildren()) { + if (it->title == "description") { + CMStringA szDescr = it->getBody(); + if (!szDescr.IsEmpty()) { + GCEVENT gce = {m_szModuleName, 0, GC_EVENT_INFORMATION}; + gce.dwFlags = GCEF_UTF8; + gce.pszID.a = pChatUser->szId; + gce.pszText.a = szDescr.c_str(); + Chat_Event(&gce); + } + } + else if (it->title == "member_add_mode") { + szRole = it->getBody(); + } + else if (it->title == "participant") { + auto *jid = it->getAttr("jid"); + + // if role isn't specified, use the default one + auto *role = it->getAttr("type"); + if (role == nullptr) + role = szRole; + + GCEVENT gce = {m_szModuleName, 0, GC_EVENT_JOIN}; + gce.dwFlags = GCEF_UTF8; + gce.pszID.a = pChatUser->szId; + gce.pszUID.a = jid; + gce.bIsMe = (jid == m_szJid); + + if (jid == szOwner) + gce.pszStatus.a = "Owner"; + else if (!mir_strcmp(role, "superadmin")) + gce.pszStatus.a = "SuperAdmin"; + else if (!mir_strcmp(role, "adminadd")) + gce.pszStatus.a = "Admin"; + else + gce.pszStatus.a = "Participant"; + + if (gce.bIsMe) + szNick = ptrA(getUStringA(DBKEY_NICK)); + else if (auto *pUser = FindUser(jid)) + szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get(); + else + szNick = WAJid(jid).user; + + gce.pszNick.a = szNick; + Chat_Event(&gce); + } + } + + if (auto *pszSubject = pGroup->getAttr("subject")) { + time_t iSubjectTime = pGroup->getAttrInt("s_t"); + auto *pszUser = pGroup->getAttr("s_o"); + if (m_szJid == pszUser) + szNick = ptrA(getUStringA(DBKEY_NICK)); + else if (auto *pUser = FindUser(pszUser)) + szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get(); + else + szNick = WAJid(pszUser).user; + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC }; + gce.dwFlags = GCEF_UTF8; + gce.pszID.a = pChatUser->szId; + gce.pszUID.a = pszUser; + gce.pszText.a = pszSubject; + gce.time = iSubjectTime; + Chat_Event(&gce); + + setUString(pChatUser->hContact, "Nick", pszSubject); + } + + pChatUser->bInited = true; + Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); + Chat_Control(m_szModuleName, wszId, SESSION_ONLINE); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int WhatsAppProto::GcEventHook(WPARAM, LPARAM lParam) +{ + GCHOOK *gch = (GCHOOK*)lParam; + if (gch == nullptr) + return 0; + + if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) + return 0; + + auto *pUser = FindUser(T2Utf(gch->si->ptszID)); + if (pUser == nullptr) + return 0; + + switch (gch->iType) { + case GC_USER_MESSAGE: + if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) { + rtrimw(gch->ptszText); + Chat_UnescapeTags(gch->ptszText); + SendTextMessage(pUser->szId, T2Utf(gch->ptszText)); + } + break; + + case GC_USER_PRIVMESS: + break; + + case GC_USER_LOGMENU: + break; + + case GC_USER_NICKLISTMENU: + break; + } + + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int WhatsAppProto::GcMenuHook(WPARAM, LPARAM lParam) +{ + GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; + if (gcmi == nullptr) + return 0; + + if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) + return 0; + + auto *pUser = FindUser(T2Utf(gcmi->pszID)); + if (pUser == nullptr) + return 0; + + if (gcmi->Type == MENU_ON_LOG) { + } + else if (gcmi->Type == MENU_ON_NICKLIST) { + } + return 0; +} diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp index 006f78dfc0..8fabeb098b 100644 --- a/protocols/WhatsApp/src/proto.cpp +++ b/protocols/WhatsApp/src/proto.cpp @@ -1,308 +1,308 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-23 George Hazan - -*/ - -#include "stdafx.h" - -struct SearchParam -{ - SearchParam(const wchar_t *_jid, LONG _id) : - jid(_jid), id(_id) - {} - - std::wstring jid; - LONG id; -}; - -static int CompareOwnMsgs(const WAOwnMessage *p1, const WAOwnMessage *p2) -{ - return strcmp(p1->szMessageId, p2->szMessageId); -} - -static int CompareUsers(const WAUser *p1, const WAUser *p2) -{ - return strcmp(p1->szId, p2->szId); -} - -static int CompareCollections(const WACollection *p1, const WACollection *p2) -{ - return strcmp(p1->szName, p2->szName); -} - -static int CompareRequests(const WARequestBase *p1, const WARequestBase *p2) -{ - return strcmp(p1->szPacketId, p2->szPacketId); -} - -WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : - PROTO(proto_name, username), - m_impl(*this), - m_signalStore(this, ""), - m_szJid(getMStringA(DBKEY_ID)), - m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), - m_arUsers(10, CompareUsers), - m_arOwnMsgs(1, CompareOwnMsgs), - m_arPersistent(1), - m_arPacketQueue(10, CompareRequests), - m_arCollections(10, CompareCollections), - - m_wszNick(this, "Nick"), - m_wszDeviceName(this, "DeviceName", L"Miranda NG"), - m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"), - m_bUsePopups(this, "UsePopups", true), - m_bUseBbcodes(this, "UseBbcodes", true), - m_bHideGroupchats(this, "HideChats", true) -{ - db_set_resident(m_szModuleName, "StatusMsg"); - - CreateProtoService(PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI); - - CreateProtoService(PS_GETAVATARINFO, &WhatsAppProto::GetAvatarInfo); - CreateProtoService(PS_GETAVATARCAPS, &WhatsAppProto::GetAvatarCaps); - CreateProtoService(PS_GETMYAVATAR, &WhatsAppProto::GetMyAvatar); - CreateProtoService(PS_SETMYAVATAR, &WhatsAppProto::SetMyAvatar); - - HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit); - - InitSync(); - InitPopups(); - InitPersistentHandlers(); - - // Create standard network connection - wchar_t descr[512]; - mir_snwprintf(descr, TranslateT("%s (server)"), m_tszUserName); - - NETLIBUSER nlu = {}; - nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; - nlu.szSettingsModule = m_szModuleName; - nlu.szDescriptiveName.w = descr; - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - // Temporary folder - CreateDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName)); - - // Avatars folder - m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + _A2T(m_szModuleName); - DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str()); - if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) - CreateDirectoryTreeW(m_tszAvatarFolder.c_str()); - - // default contacts group - if (m_tszDefaultGroup == NULL) - m_tszDefaultGroup = mir_wstrdup(L"WhatsApp"); - Clist_GroupCreate(0, m_tszDefaultGroup); - - // groupchat initialization - GCREGISTER gcr = {}; - gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; - gcr.ptszDispName = m_tszUserName; - gcr.pszModule = m_szModuleName; - Chat_Register(&gcr); - - HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); - HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); -} - -WhatsAppProto::~WhatsAppProto() -{ -} - -///////////////////////////////////////////////////////////////////////////////////////// -// OnErase - remove temporary folder for account - -const char *pszNeededItems[] = { - "AM_BaseProto", "DefaultGroup", "DeviceName", "HideChats", "NLlog", "Nick" -}; - -static int sttEnumFunc(const char *szSetting, void *param) -{ - for (auto &it : pszNeededItems) - if (!mir_strcmp(it, szSetting)) - return 0; - - auto *pList = (LIST*)param; - pList->insert(mir_strdup(szSetting)); - return 0; -} - -void WhatsAppProto::OnErase() -{ - m_bUnregister = true; - ServerThreadWorker(); - - // remove all temporary data from database & disk folder - LIST arSettings(50); - db_enum_settings(0, sttEnumFunc, m_szModuleName, &arSettings); - for (auto &it : arSettings) { - delSetting(it); - mir_free(it); - } - - DeleteDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName), false); - - m_szJid.Empty(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// OnModulesLoaded emulator for an account - -void WhatsAppProto::OnModulesLoaded() -{ - // initialize contacts cache - if (!m_szJid.IsEmpty()) - m_arUsers.insert(new WAUser(0, m_szJid, false)); - - for (auto &cc : AccContacts()) { - bool bIsChat = isChatRoom(cc); - CMStringA szId(getMStringA(cc, bIsChat ? "ChatRoomID" : DBKEY_ID)); - if (!szId.IsEmpty()) - m_arUsers.insert(new WAUser(cc, szId, bIsChat)); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// PROTO_INTERFACE implementation - -MCONTACT WhatsAppProto::AddToList(int flags, PROTOSEARCHRESULT *psr) -{ - if (psr->id.w == nullptr) - return NULL; - - auto *pUser = AddUser(T2Utf(psr->id.w), (flags & PALF_TEMPORARY) != 0); - db_unset(pUser->hContact, "CList", "NotOnList"); - - return pUser->hContact; -} - -INT_PTR WhatsAppProto::GetCaps(int type, MCONTACT) -{ - switch (type) { - case PFLAGNUM_1: - return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; - case PFLAGNUM_2: - return PF2_ONLINE; - case PFLAGNUM_3: - return 0; - case PFLAGNUM_4: - return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; - case PFLAGNUM_5: - return 0; - case PFLAG_UNIQUEIDTEXT: - return (DWORD_PTR)L"WhatsApp ID"; - } - return 0; -} - -int WhatsAppProto::SetStatus(int iNewStatus) -{ - if (m_iDesiredStatus == iNewStatus) - return 0; - - if (!mir_wstrlen(m_wszNick)) { - Popup(0, LPGENW("Connection cannot be established! You have not completed all necessary fields (Nick)."), LPGENW("Error")); - return 0; - } - - int oldStatus = m_iStatus; - - // Routing statuses not supported by WhatsApp - switch (iNewStatus) { - case ID_STATUS_OFFLINE: - m_iDesiredStatus = iNewStatus; - break; - - case ID_STATUS_ONLINE: - case ID_STATUS_FREECHAT: - default: - m_iDesiredStatus = ID_STATUS_ONLINE; - break; - } - - if (m_iDesiredStatus == ID_STATUS_OFFLINE) { - SetServerStatus(m_iDesiredStatus); - - if (m_hServerConn != nullptr) - Netlib_Shutdown(m_hServerConn); - - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else if (m_hServerConn == nullptr && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - ForkThread(&WhatsAppProto::ServerThread); - } - else if (m_hServerConn != nullptr) { - SetServerStatus(m_iDesiredStatus); - - m_iStatus = m_iDesiredStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - } - else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg) -{ - ptrA jid(getStringA(hContact, DBKEY_ID)); - if (jid == nullptr || pszMsg == nullptr) - return 0; - - if (!isOnline()) { - debugLogA("No connection"); - return 0; - } - - return SendTextMessage(jid, pszMsg); -} - -int WhatsAppProto::UserIsTyping(MCONTACT hContact, int type) -{ - if (hContact && isOnline()) { - ptrA jid(getStringA(hContact, DBKEY_ID)); - if (jid && isOnline()) { - WSSendNode( - WANode("chatstates") << CHAR_PARAM("to", jid) << XCHILD((type == PROTOTYPE_SELFTYPING_ON) ? "composing" : "paused")); - } - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// contacts search - -void WhatsAppProto::SearchAckThread(void *targ) -{ - Sleep(100); - - SearchParam *param = (SearchParam*)targ; - PROTOSEARCHRESULT psr = {}; - psr.cbSize = sizeof(psr); - psr.flags = PSR_UNICODE; - psr.nick.w = psr.firstName.w = psr.lastName.w = L""; - psr.id.w = (wchar_t*)param->jid.c_str(); - - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)param->id, (LPARAM)&psr); - ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)param->id, 0); - - delete param; -} - -HANDLE WhatsAppProto::SearchBasic(const wchar_t* id) -{ - if (!isOnline()) - return nullptr; - - // fake - we always accept search - SearchParam *param = new SearchParam(id, -1); - ForkThread(&WhatsAppProto::SearchAckThread, param); - return (HANDLE)param->id; -} +/* + +WhatsApp plugin for Miranda NG +Copyright © 2019-23 George Hazan + +*/ + +#include "stdafx.h" + +struct SearchParam +{ + SearchParam(const wchar_t *_jid, LONG _id) : + jid(_jid), id(_id) + {} + + std::wstring jid; + LONG id; +}; + +static int CompareOwnMsgs(const WAOwnMessage *p1, const WAOwnMessage *p2) +{ + return strcmp(p1->szMessageId, p2->szMessageId); +} + +static int CompareUsers(const WAUser *p1, const WAUser *p2) +{ + return strcmp(p1->szId, p2->szId); +} + +static int CompareCollections(const WACollection *p1, const WACollection *p2) +{ + return strcmp(p1->szName, p2->szName); +} + +static int CompareRequests(const WARequestBase *p1, const WARequestBase *p2) +{ + return strcmp(p1->szPacketId, p2->szPacketId); +} + +WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : + PROTO(proto_name, username), + m_impl(*this), + m_signalStore(this, ""), + m_szJid(getMStringA(DBKEY_ID)), + m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), + m_arUsers(10, CompareUsers), + m_arOwnMsgs(1, CompareOwnMsgs), + m_arPersistent(1), + m_arPacketQueue(10, CompareRequests), + m_arCollections(10, CompareCollections), + + m_wszNick(this, "Nick"), + m_wszDeviceName(this, "DeviceName", L"Miranda NG"), + m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"), + m_bUsePopups(this, "UsePopups", true), + m_bUseBbcodes(this, "UseBbcodes", true), + m_bHideGroupchats(this, "HideChats", true) +{ + db_set_resident(m_szModuleName, "StatusMsg"); + + CreateProtoService(PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI); + + CreateProtoService(PS_GETAVATARINFO, &WhatsAppProto::GetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &WhatsAppProto::GetAvatarCaps); + CreateProtoService(PS_GETMYAVATAR, &WhatsAppProto::GetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &WhatsAppProto::SetMyAvatar); + + HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit); + + InitSync(); + InitPopups(); + InitPersistentHandlers(); + + // Create standard network connection + wchar_t descr[512]; + mir_snwprintf(descr, TranslateT("%s (server)"), m_tszUserName); + + NETLIBUSER nlu = {}; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szSettingsModule = m_szModuleName; + nlu.szDescriptiveName.w = descr; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + // Temporary folder + CreateDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName)); + + // Avatars folder + m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + _A2T(m_szModuleName); + DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str()); + if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + CreateDirectoryTreeW(m_tszAvatarFolder.c_str()); + + // default contacts group + if (m_tszDefaultGroup == NULL) + m_tszDefaultGroup = mir_wstrdup(L"WhatsApp"); + Clist_GroupCreate(0, m_tszDefaultGroup); + + // groupchat initialization + GCREGISTER gcr = {}; + gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE; + gcr.ptszDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + Chat_Register(&gcr); + + HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); + HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); +} + +WhatsAppProto::~WhatsAppProto() +{ +} + +///////////////////////////////////////////////////////////////////////////////////////// +// OnErase - remove temporary folder for account + +const char *pszNeededItems[] = { + "AM_BaseProto", "DefaultGroup", "DeviceName", "HideChats", "NLlog", "Nick" +}; + +static int sttEnumFunc(const char *szSetting, void *param) +{ + for (auto &it : pszNeededItems) + if (!mir_strcmp(it, szSetting)) + return 0; + + auto *pList = (LIST*)param; + pList->insert(mir_strdup(szSetting)); + return 0; +} + +void WhatsAppProto::OnErase() +{ + m_bUnregister = true; + ServerThreadWorker(); + + // remove all temporary data from database & disk folder + LIST arSettings(50); + db_enum_settings(0, sttEnumFunc, m_szModuleName, &arSettings); + for (auto &it : arSettings) { + delSetting(it); + mir_free(it); + } + + DeleteDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName), false); + + m_szJid.Empty(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// OnModulesLoaded emulator for an account + +void WhatsAppProto::OnModulesLoaded() +{ + // initialize contacts cache + if (!m_szJid.IsEmpty()) + m_arUsers.insert(new WAUser(0, m_szJid, false)); + + for (auto &cc : AccContacts()) { + bool bIsChat = isChatRoom(cc); + CMStringA szId(getMStringA(cc, bIsChat ? "ChatRoomID" : DBKEY_ID)); + if (!szId.IsEmpty()) + m_arUsers.insert(new WAUser(cc, szId, bIsChat)); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// PROTO_INTERFACE implementation + +MCONTACT WhatsAppProto::AddToList(int flags, PROTOSEARCHRESULT *psr) +{ + if (psr->id.w == nullptr) + return NULL; + + auto *pUser = AddUser(T2Utf(psr->id.w), (flags & PALF_TEMPORARY) != 0); + db_unset(pUser->hContact, "CList", "NotOnList"); + + return pUser->hContact; +} + +INT_PTR WhatsAppProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_3: + return 0; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; + case PFLAGNUM_5: + return 0; + case PFLAG_UNIQUEIDTEXT: + return (DWORD_PTR)L"WhatsApp ID"; + } + return 0; +} + +int WhatsAppProto::SetStatus(int iNewStatus) +{ + if (m_iDesiredStatus == iNewStatus) + return 0; + + if (!mir_wstrlen(m_wszNick)) { + Popup(0, LPGENW("Connection cannot be established! You have not completed all necessary fields (Nick)."), LPGENW("Error")); + return 0; + } + + int oldStatus = m_iStatus; + + // Routing statuses not supported by WhatsApp + switch (iNewStatus) { + case ID_STATUS_OFFLINE: + m_iDesiredStatus = iNewStatus; + break; + + case ID_STATUS_ONLINE: + case ID_STATUS_FREECHAT: + default: + m_iDesiredStatus = ID_STATUS_ONLINE; + break; + } + + if (m_iDesiredStatus == ID_STATUS_OFFLINE) { + SetServerStatus(m_iDesiredStatus); + + if (m_hServerConn != nullptr) + Netlib_Shutdown(m_hServerConn); + + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else if (m_hServerConn == nullptr && !IsStatusConnecting(m_iStatus)) { + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + ForkThread(&WhatsAppProto::ServerThread); + } + else if (m_hServerConn != nullptr) { + SetServerStatus(m_iDesiredStatus); + + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + } + else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg) +{ + ptrA jid(getStringA(hContact, DBKEY_ID)); + if (jid == nullptr || pszMsg == nullptr) + return 0; + + if (!isOnline()) { + debugLogA("No connection"); + return 0; + } + + return SendTextMessage(jid, pszMsg); +} + +int WhatsAppProto::UserIsTyping(MCONTACT hContact, int type) +{ + if (hContact && isOnline()) { + ptrA jid(getStringA(hContact, DBKEY_ID)); + if (jid && isOnline()) { + WSSendNode( + WANode("chatstates") << CHAR_PARAM("to", jid) << XCHILD((type == PROTOTYPE_SELFTYPING_ON) ? "composing" : "paused")); + } + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// contacts search + +void WhatsAppProto::SearchAckThread(void *targ) +{ + Sleep(100); + + SearchParam *param = (SearchParam*)targ; + PROTOSEARCHRESULT psr = {}; + psr.cbSize = sizeof(psr); + psr.flags = PSR_UNICODE; + psr.nick.w = psr.firstName.w = psr.lastName.w = L""; + psr.id.w = (wchar_t*)param->jid.c_str(); + + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)param->id, (LPARAM)&psr); + ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)param->id, 0); + + delete param; +} + +HANDLE WhatsAppProto::SearchBasic(const wchar_t* id) +{ + if (!isOnline()) + return nullptr; + + // fake - we always accept search + SearchParam *param = new SearchParam(id, -1); + ForkThread(&WhatsAppProto::SearchAckThread, param); + return (HANDLE)param->id; +} diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h index dbcf57b597..8ec4593767 100644 --- a/protocols/WhatsApp/src/proto.h +++ b/protocols/WhatsApp/src/proto.h @@ -1,508 +1,508 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-23 George Hazan - -*/ - -#if !defined(PROTO_H) -#define PROTO_H - -#define S_WHATSAPP_NET "@s.whatsapp.net" -#define APP_VERSION "2.2230.15" -#define KEY_BUNDLE_TYPE "\x05" - -class WhatsAppProto; -typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node); -typedef void (WhatsAppProto:: *WA_PKT_HANDLER_FULL)(const WANode &node, void *pUserInfo); - -struct WAMSG -{ - union { - uint32_t dwFlags = 0; - struct { - bool bPrivateChat : 1; - bool bGroupChat : 1; - bool bDirectStatus : 1; - bool bOtherStatus : 1; - bool bPeerBroadcast : 1; - bool bOtherBroadcast : 1; - bool bOffline : 1; - }; - }; -}; - -struct WAMediaKeys -{ - WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType); - - uint8_t iv[16]; - uint8_t cipherKey[32]; - uint8_t macKey[64]; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// own requests - -struct WARequestBase -{ - WARequestBase(const CMStringA &_1) : - szPacketId(_1) - {} - virtual ~WARequestBase() {} - - CMStringA szPacketId; - - virtual void Execute(WhatsAppProto *ppro, const WANode &node) = 0; -}; - -class WARequestSimple : public WARequestBase -{ - WA_PKT_HANDLER pHandler; - -public: - WARequestSimple(const CMStringA &_1, WA_PKT_HANDLER _2) : - WARequestBase(_1), - pHandler(_2) - {} - - void Execute(WhatsAppProto *ppro, const WANode &node) override - { - (ppro->*pHandler)(node); - } -}; - -class WARequestParam : public WARequestBase -{ - WA_PKT_HANDLER_FULL pHandler; - void *pUserInfo; - -public: - WARequestParam(const CMStringA &_1, WA_PKT_HANDLER_FULL _2, void *_3) : - WARequestBase(_1), - pHandler(_2), - pUserInfo(_3) - {} - - void Execute(WhatsAppProto *ppro, const WANode &node) override - { - (ppro->*pHandler)(node, pUserInfo); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct WAPersistentHandler -{ - WAPersistentHandler(const char *_1, const char *_2, const char *_3, const char *_4, WA_PKT_HANDLER _5) : - pszTitle(_1), pszType(_2), pszXmlns(_3), pszChild(_4), pHandler(_5) - {} - - const char *pszTitle, *pszType, *pszXmlns, *pszChild; - WA_PKT_HANDLER pHandler; -}; - -struct WAUser -{ - WAUser(MCONTACT _1, const char *_2, bool _3 = false) : - hContact(_1), - szId(mir_strdup(_2)), - bIsGroupChat(_3), - arDevices(1) - { - } - - ~WAUser() - { - mir_free(szId); - } - - MCONTACT hContact; - DWORD dwModifyTag = 0; - char *szId; - bool bInited = false, bIsGroupChat, bDeviceInit = false; - SESSION_INFO *si = 0; - OBJLIST arDevices; - time_t m_timer1 = 0, m_timer2 = 0; -}; - -struct WAOwnMessage -{ - WAOwnMessage(int _1, const char *_2, const char *_3) : - pktId(_1), - szJid(_2), - szMessageId(_3) - {} - - int pktId; - CMStringA szJid, szMessageId; -}; - -struct WACollection -{ - WACollection(const char *_1, int _2 = 0) : - szName(mir_strdup(_1)), - version(_2) - {} - - ptrA szName; - int version; - - LT_HASH hash; - std::map indexValueMap; -}; - -class WANoise -{ - friend class WhatsAppProto; - - WhatsAppProto *ppro; - uint32_t readCounter = 0, writeCounter = 0; - bool bInitFinished = false, bSendIntro = false; - MBinBuffer salt, encKey, decKey; - uint8_t hash[32]; - - struct { - MBinBuffer priv, pub; - } noiseKeys, ephemeral; - - void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read); - void mixIntoKey(const void *n, const void *p); - void updateHash(const void *pData, size_t cbLen); - -public: - WANoise(WhatsAppProto *_ppro); - - void finish(); - void init(); - - MBinBuffer decrypt(const void *pData, size_t cbLen); - MBinBuffer encrypt(const void *pData, size_t cbLen); - - size_t decodeFrame(const uint8_t *&pData, size_t &cbLen); - MBinBuffer encodeFrame(const void *pData, size_t cbLen); -}; - -class MSignalSession : public MZeroedObject -{ - friend class MSignalStore; - signal_protocol_address address; - session_cipher *cipher = nullptr; - -public: - CMStringA szName; - MBinBuffer sessionData; - - MSignalSession(const CMStringA &_1, int _2); - ~MSignalSession(); - - bool hasAddress(const char *name, size_t name_len) const; - - __forceinline session_cipher* getCipher(void) const { return cipher; } - __forceinline int getDeviceId() const { return address.device_id; } - CMStringA getSetting() const; -}; - -class MSignalStore -{ - void init(); - - signal_context *m_pContext; - signal_protocol_store_context *m_pStore; - - void importPublicKey(ec_public_key **result, MBinBuffer &buf); - -public: - PROTO_INTERFACE *pProto; - const char *prefix; - - OBJLIST arSessions; - - struct - { - MBinBuffer priv, pub; - } - signedIdentity; - - struct - { - MBinBuffer priv, pub, signature; - uint32_t keyid; - } - preKey; - - MSignalStore(PROTO_INTERFACE *_1, const char *_2); - ~MSignalStore(); - - __forceinline signal_context *CTX() const { return m_pContext; } - - MSignalSession* createSession(const CMStringA &szName, int deviceId); - MSignalSession* getSession(const signal_protocol_address *address); - void injectSession(const char *szJid, const WANode *pNode, const WANode *pKey); - - MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted); - MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted); - - MBinBuffer encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey); - MBinBuffer encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type); - - MBinBuffer encodeSignedIdentity(bool); - void generatePrekeys(int count); - - void logError(int code, const char *szMessage); - - void processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg); -}; - -class WhatsAppProto : public PROTO -{ - friend class WANoise; - friend class CWhatsAppQRDlg; - friend class COptionsDlg; - - class CWhatsAppProtoImpl - { - friend class WhatsAppProto; - WhatsAppProto &m_proto; - - CTimer m_keepAlive, m_resyncApp; - void OnKeepAlive(CTimer *) - { m_proto.SendKeepAlive(); - } - void OnResync(CTimer *pTimer) - { - pTimer->Stop(); - m_proto.ResyncAll(); - } - - CWhatsAppProtoImpl(WhatsAppProto &pro) : - m_proto(pro), - m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)), - m_resyncApp(Miranda_GetSystemWindow(), UINT_PTR(this)+1) - { - m_keepAlive.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnKeepAlive); - m_resyncApp.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnResync); - } - } m_impl; - - bool m_bTerminated, m_bRespawn, m_bUpdatedPrekeys, m_bUnregister; - ptrW m_tszDefaultGroup; - - CMStringA m_szJid; - CMStringW m_tszAvatarFolder; - - EVP_PKEY *m_pKeys; // private & public keys - WANoise *m_noise; - - void UploadMorePrekeys(); - - // App state management - OBJLIST m_arCollections; - - void InitSync(void); - void ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data); - void ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet); - void ProcessHistorySync(const Wa__HistorySync *pSync); - void ResyncServer(const OBJLIST &task); - void ResyncAll(void); - - __forceinline WACollection *FindCollection(const char *pszName) - { return m_arCollections.find((WACollection *)&pszName); - } - - // Contacts management ///////////////////////////////////////////////////////////////// - - mir_cs m_csUsers; - OBJLIST m_arUsers; - - mir_cs m_csOwnMessages; - OBJLIST m_arOwnMsgs; - - WAUser* FindUser(const char *szId); - WAUser* AddUser(const char *szId, bool bTemporary); - - // Group chats ///////////////////////////////////////////////////////////////////////// - - void GC_RefreshMetadata(); - void GC_GetAllMetadata(); - void GC_ParseMetadata(const WANode *pGroup); - - int __cdecl GcEventHook(WPARAM, LPARAM); - int __cdecl GcMenuHook(WPARAM, LPARAM); - - // UI ////////////////////////////////////////////////////////////////////////////////// - - void CloseQrDialog(); - bool ShowQrCode(const CMStringA &ref); - - /// Network //////////////////////////////////////////////////////////////////////////// - - time_t m_lastRecvTime; - HNETLIBCONN m_hServerConn; - - mir_cs m_csPacketQueue; - OBJLIST m_arPacketQueue; - - LIST m_arPersistent; - WA_PKT_HANDLER FindPersistentHandler(const WANode &node); - - int m_iPacketId; - uint16_t m_wMsgPrefix[2]; - CMStringA GenerateMessageId(); - CMStringA GetMessageText(const Wa__Message *pMessage); - void GetMessageContent(CMStringA &txt, const char *szType, const char *szUrl, const char *szMimetype, const char *szDirectPath, const ProtobufCBinaryData &szMediaKey, const char *szCaption = nullptr); - void ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg); - bool CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig); - - void ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead); - - bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf); - int WSSend(const ProtobufCMessage &msg); - int WSSendNode(WANode &node); - int WSSendNode(WANode &node, WA_PKT_HANDLER); - int WSSendNode(WANode &node, WA_PKT_HANDLER_FULL, void *pUserInfo); - - MBinBuffer DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszType); - CMStringW GetTmpFileName(const char *pszClass, const char *addition); - - void OnLoggedIn(void); - void OnLoggedOut(void); - void ProcessFailure(int code); - void ServerThreadWorker(void); - void ShutdownSession(void); - - void SendAck(const WANode &node); - void SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType); - void SendKeepAlive(void); - int SendTextMessage(const char *jid, const char *pszMsg); - void SendUnregister(void); - void SendUsync(const LIST &jids, void *pUserInfo); - void SetServerStatus(int iStatus); - - void FinishTask(WASendTask *pTask); - void SendTask(WASendTask *pTask); - - /// Popups ///////////////////////////////////////////////////////////////////////////// - - HANDLE m_hPopupClass; - CMOption m_bUsePopups; - - void InitPopups(void); - void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); - - /// Request handlers /////////////////////////////////////////////////////////////////// - - void OnProcessHandshake(const uint8_t *pData, int cbLen); - - void InitPersistentHandlers(); - void OnAccountSync(const WANode &node); - void OnIqBlockList(const WANode &node); - void OnIqCountPrekeys(const WANode &node); - void OnIqDoNothing(const WANode &node); - void OnIqGcGetAllMetadata(const WANode &node); - void OnIqGetAvatar(const WANode &node); - void OnIqGetKeys(const WANode &node, void *pUserInfo); - void OnIqGetUsync(const WANode &node, void *pUserInfo); - void OnIqPairDevice(const WANode &node); - void OnIqPairSuccess(const WANode &node); - void OnIqResult(const WANode &node); - void OnIqServerSync(const WANode &node); - void OnNotifyAny(const WANode &node); - void OnNotifyDevices(const WANode &node); - void OnNotifyEncrypt(const WANode &node); - void OnNotifyPicture(const WANode &node); - void OnReceiveAck(const WANode &node); - void OnReceiveChatState(const WANode &node); - void OnReceiveFailure(const WANode &node); - void OnReceiveInfo(const WANode &node); - void OnReceiveMessage(const WANode &node); - void OnReceiveReceipt(const WANode &node); - void OnServerSync(const WANode &node); - void OnStreamError(const WANode &node); - void OnSuccess(const WANode &node); - - // Signal - MSignalStore m_signalStore; - - // Binary packets - void ProcessBinaryPacket(const uint8_t *pData, size_t cbLen); - - // unzip operations - MBinBuffer unzip(const MBinBuffer &src); - - /// Avatars //////////////////////////////////////////////////////////////////////////// - CMStringW GetAvatarFileName(MCONTACT hContact); - void ServerFetchAvatar(const char *jid); - - INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); - INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); - INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM); - INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM); - -public: - WhatsAppProto(const char *proto_name, const wchar_t *username); - ~WhatsAppProto(); - - __forceinline bool isOnline() const - { return m_hServerConn != 0; - } - - __forceinline void writeStr(const char *pszSetting, const JSONNode &node) - { - CMStringW str(node.as_mstring()); - if (!str.IsEmpty()) - setWString(pszSetting, str); - } - - class CWhatsAppQRDlg *m_pQRDlg; - - // PROTO_INTERFACE ///////////////////////////////////////////////////////////////////// - - MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; - INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; - HANDLE SearchBasic(const wchar_t* id) override; - int SendMsg(MCONTACT hContact, int flags, const char* msg) override; - int SetStatus(int iNewStatus) override; - int UserIsTyping(MCONTACT hContact, int type) override; - - void OnErase() override; - void OnModulesLoaded() override; - - // Services //////////////////////////////////////////////////////////////////////////// - - INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - - // Events ////////////////////////////////////////////////////////////////////////////// - - int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); - - // Options ///////////////////////////////////////////////////////////////////////////// - - CMOption m_wszNick; // your nick name in presence - CMOption m_wszDeviceName; // how do you see Miranda in mobile phone - CMOption m_wszDefaultGroup; // clist group to store contacts - CMOption m_bHideGroupchats; // do not open chat windows on creation - CMOption m_bUseBbcodes; // use extended markup for messages - - // Processing Threads ////////////////////////////////////////////////////////////////// - - void __cdecl SearchAckThread(void*); - void __cdecl ServerThread(void*); -}; - -struct CMPlugin : public ACCPROTOPLUGIN -{ - HNETLIBUSER hAvatarUser = nullptr; - HNETLIBCONN hAvatarConn = nullptr; - bool SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai); - - bool bHasMessageState = false; - - CMPlugin(); - - int Load() override; - int Unload() override; -}; - -#endif +/* + +WhatsApp plugin for Miranda NG +Copyright © 2019-23 George Hazan + +*/ + +#if !defined(PROTO_H) +#define PROTO_H + +#define S_WHATSAPP_NET "@s.whatsapp.net" +#define APP_VERSION "2.2230.15" +#define KEY_BUNDLE_TYPE "\x05" + +class WhatsAppProto; +typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node); +typedef void (WhatsAppProto:: *WA_PKT_HANDLER_FULL)(const WANode &node, void *pUserInfo); + +struct WAMSG +{ + union { + uint32_t dwFlags = 0; + struct { + bool bPrivateChat : 1; + bool bGroupChat : 1; + bool bDirectStatus : 1; + bool bOtherStatus : 1; + bool bPeerBroadcast : 1; + bool bOtherBroadcast : 1; + bool bOffline : 1; + }; + }; +}; + +struct WAMediaKeys +{ + WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType); + + uint8_t iv[16]; + uint8_t cipherKey[32]; + uint8_t macKey[64]; +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// own requests + +struct WARequestBase +{ + WARequestBase(const CMStringA &_1) : + szPacketId(_1) + {} + virtual ~WARequestBase() {} + + CMStringA szPacketId; + + virtual void Execute(WhatsAppProto *ppro, const WANode &node) = 0; +}; + +class WARequestSimple : public WARequestBase +{ + WA_PKT_HANDLER pHandler; + +public: + WARequestSimple(const CMStringA &_1, WA_PKT_HANDLER _2) : + WARequestBase(_1), + pHandler(_2) + {} + + void Execute(WhatsAppProto *ppro, const WANode &node) override + { + (ppro->*pHandler)(node); + } +}; + +class WARequestParam : public WARequestBase +{ + WA_PKT_HANDLER_FULL pHandler; + void *pUserInfo; + +public: + WARequestParam(const CMStringA &_1, WA_PKT_HANDLER_FULL _2, void *_3) : + WARequestBase(_1), + pHandler(_2), + pUserInfo(_3) + {} + + void Execute(WhatsAppProto *ppro, const WANode &node) override + { + (ppro->*pHandler)(node, pUserInfo); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct WAPersistentHandler +{ + WAPersistentHandler(const char *_1, const char *_2, const char *_3, const char *_4, WA_PKT_HANDLER _5) : + pszTitle(_1), pszType(_2), pszXmlns(_3), pszChild(_4), pHandler(_5) + {} + + const char *pszTitle, *pszType, *pszXmlns, *pszChild; + WA_PKT_HANDLER pHandler; +}; + +struct WAUser +{ + WAUser(MCONTACT _1, const char *_2, bool _3 = false) : + hContact(_1), + szId(mir_strdup(_2)), + bIsGroupChat(_3), + arDevices(1) + { + } + + ~WAUser() + { + mir_free(szId); + } + + MCONTACT hContact; + DWORD dwModifyTag = 0; + char *szId; + bool bInited = false, bIsGroupChat, bDeviceInit = false; + SESSION_INFO *si = 0; + OBJLIST arDevices; + time_t m_timer1 = 0, m_timer2 = 0; +}; + +struct WAOwnMessage +{ + WAOwnMessage(int _1, const char *_2, const char *_3) : + pktId(_1), + szJid(_2), + szMessageId(_3) + {} + + int pktId; + CMStringA szJid, szMessageId; +}; + +struct WACollection +{ + WACollection(const char *_1, int _2 = 0) : + szName(mir_strdup(_1)), + version(_2) + {} + + ptrA szName; + int version; + + LT_HASH hash; + std::map indexValueMap; +}; + +class WANoise +{ + friend class WhatsAppProto; + + WhatsAppProto *ppro; + uint32_t readCounter = 0, writeCounter = 0; + bool bInitFinished = false, bSendIntro = false; + MBinBuffer salt, encKey, decKey; + uint8_t hash[32]; + + struct { + MBinBuffer priv, pub; + } noiseKeys, ephemeral; + + void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read); + void mixIntoKey(const void *n, const void *p); + void updateHash(const void *pData, size_t cbLen); + +public: + WANoise(WhatsAppProto *_ppro); + + void finish(); + void init(); + + MBinBuffer decrypt(const void *pData, size_t cbLen); + MBinBuffer encrypt(const void *pData, size_t cbLen); + + size_t decodeFrame(const uint8_t *&pData, size_t &cbLen); + MBinBuffer encodeFrame(const void *pData, size_t cbLen); +}; + +class MSignalSession : public MZeroedObject +{ + friend class MSignalStore; + signal_protocol_address address; + session_cipher *cipher = nullptr; + +public: + CMStringA szName; + MBinBuffer sessionData; + + MSignalSession(const CMStringA &_1, int _2); + ~MSignalSession(); + + bool hasAddress(const char *name, size_t name_len) const; + + __forceinline session_cipher* getCipher(void) const { return cipher; } + __forceinline int getDeviceId() const { return address.device_id; } + CMStringA getSetting() const; +}; + +class MSignalStore +{ + void init(); + + signal_context *m_pContext; + signal_protocol_store_context *m_pStore; + + void importPublicKey(ec_public_key **result, MBinBuffer &buf); + +public: + PROTO_INTERFACE *pProto; + const char *prefix; + + OBJLIST arSessions; + + struct + { + MBinBuffer priv, pub; + } + signedIdentity; + + struct + { + MBinBuffer priv, pub, signature; + uint32_t keyid; + } + preKey; + + MSignalStore(PROTO_INTERFACE *_1, const char *_2); + ~MSignalStore(); + + __forceinline signal_context *CTX() const { return m_pContext; } + + MSignalSession* createSession(const CMStringA &szName, int deviceId); + MSignalSession* getSession(const signal_protocol_address *address); + void injectSession(const char *szJid, const WANode *pNode, const WANode *pKey); + + MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted); + MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted); + + MBinBuffer encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey); + MBinBuffer encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type); + + MBinBuffer encodeSignedIdentity(bool); + void generatePrekeys(int count); + + void logError(int code, const char *szMessage); + + void processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg); +}; + +class WhatsAppProto : public PROTO +{ + friend class WANoise; + friend class CWhatsAppQRDlg; + friend class COptionsDlg; + + class CWhatsAppProtoImpl + { + friend class WhatsAppProto; + WhatsAppProto &m_proto; + + CTimer m_keepAlive, m_resyncApp; + void OnKeepAlive(CTimer *) + { m_proto.SendKeepAlive(); + } + void OnResync(CTimer *pTimer) + { + pTimer->Stop(); + m_proto.ResyncAll(); + } + + CWhatsAppProtoImpl(WhatsAppProto &pro) : + m_proto(pro), + m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)), + m_resyncApp(Miranda_GetSystemWindow(), UINT_PTR(this)+1) + { + m_keepAlive.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnKeepAlive); + m_resyncApp.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnResync); + } + } m_impl; + + bool m_bTerminated, m_bRespawn, m_bUpdatedPrekeys, m_bUnregister; + ptrW m_tszDefaultGroup; + + CMStringA m_szJid; + CMStringW m_tszAvatarFolder; + + EVP_PKEY *m_pKeys; // private & public keys + WANoise *m_noise; + + void UploadMorePrekeys(); + + // App state management + OBJLIST m_arCollections; + + void InitSync(void); + void ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data); + void ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet); + void ProcessHistorySync(const Wa__HistorySync *pSync); + void ResyncServer(const OBJLIST &task); + void ResyncAll(void); + + __forceinline WACollection *FindCollection(const char *pszName) + { return m_arCollections.find((WACollection *)&pszName); + } + + // Contacts management ///////////////////////////////////////////////////////////////// + + mir_cs m_csUsers; + OBJLIST m_arUsers; + + mir_cs m_csOwnMessages; + OBJLIST m_arOwnMsgs; + + WAUser* FindUser(const char *szId); + WAUser* AddUser(const char *szId, bool bTemporary); + + // Group chats ///////////////////////////////////////////////////////////////////////// + + void GC_RefreshMetadata(); + void GC_GetAllMetadata(); + void GC_ParseMetadata(const WANode *pGroup); + + int __cdecl GcEventHook(WPARAM, LPARAM); + int __cdecl GcMenuHook(WPARAM, LPARAM); + + // UI ////////////////////////////////////////////////////////////////////////////////// + + void CloseQrDialog(); + bool ShowQrCode(const CMStringA &ref); + + /// Network //////////////////////////////////////////////////////////////////////////// + + time_t m_lastRecvTime; + HNETLIBCONN m_hServerConn; + + mir_cs m_csPacketQueue; + OBJLIST m_arPacketQueue; + + LIST m_arPersistent; + WA_PKT_HANDLER FindPersistentHandler(const WANode &node); + + int m_iPacketId; + uint16_t m_wMsgPrefix[2]; + CMStringA GenerateMessageId(); + CMStringA GetMessageText(const Wa__Message *pMessage); + void GetMessageContent(CMStringA &txt, const char *szType, const char *szUrl, const char *szMimetype, const char *szDirectPath, const ProtobufCBinaryData &szMediaKey, const char *szCaption = nullptr); + void ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg); + bool CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig); + + void ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead); + + bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf); + int WSSend(const ProtobufCMessage &msg); + int WSSendNode(WANode &node); + int WSSendNode(WANode &node, WA_PKT_HANDLER); + int WSSendNode(WANode &node, WA_PKT_HANDLER_FULL, void *pUserInfo); + + MBinBuffer DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszType); + CMStringW GetTmpFileName(const char *pszClass, const char *addition); + + void OnLoggedIn(void); + void OnLoggedOut(void); + void ProcessFailure(int code); + void ServerThreadWorker(void); + void ShutdownSession(void); + + void SendAck(const WANode &node); + void SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType); + void SendKeepAlive(void); + int SendTextMessage(const char *jid, const char *pszMsg); + void SendUnregister(void); + void SendUsync(const LIST &jids, void *pUserInfo); + void SetServerStatus(int iStatus); + + void FinishTask(WASendTask *pTask); + void SendTask(WASendTask *pTask); + + /// Popups ///////////////////////////////////////////////////////////////////////////// + + HANDLE m_hPopupClass; + CMOption m_bUsePopups; + + void InitPopups(void); + void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle); + + /// Request handlers /////////////////////////////////////////////////////////////////// + + void OnProcessHandshake(const uint8_t *pData, int cbLen); + + void InitPersistentHandlers(); + void OnAccountSync(const WANode &node); + void OnIqBlockList(const WANode &node); + void OnIqCountPrekeys(const WANode &node); + void OnIqDoNothing(const WANode &node); + void OnIqGcGetAllMetadata(const WANode &node); + void OnIqGetAvatar(const WANode &node); + void OnIqGetKeys(const WANode &node, void *pUserInfo); + void OnIqGetUsync(const WANode &node, void *pUserInfo); + void OnIqPairDevice(const WANode &node); + void OnIqPairSuccess(const WANode &node); + void OnIqResult(const WANode &node); + void OnIqServerSync(const WANode &node); + void OnNotifyAny(const WANode &node); + void OnNotifyDevices(const WANode &node); + void OnNotifyEncrypt(const WANode &node); + void OnNotifyPicture(const WANode &node); + void OnReceiveAck(const WANode &node); + void OnReceiveChatState(const WANode &node); + void OnReceiveFailure(const WANode &node); + void OnReceiveInfo(const WANode &node); + void OnReceiveMessage(const WANode &node); + void OnReceiveReceipt(const WANode &node); + void OnServerSync(const WANode &node); + void OnStreamError(const WANode &node); + void OnSuccess(const WANode &node); + + // Signal + MSignalStore m_signalStore; + + // Binary packets + void ProcessBinaryPacket(const uint8_t *pData, size_t cbLen); + + // unzip operations + MBinBuffer unzip(const MBinBuffer &src); + + /// Avatars //////////////////////////////////////////////////////////////////////////// + CMStringW GetAvatarFileName(MCONTACT hContact); + void ServerFetchAvatar(const char *jid); + + INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); + INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); + INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM); + INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM); + +public: + WhatsAppProto(const char *proto_name, const wchar_t *username); + ~WhatsAppProto(); + + __forceinline bool isOnline() const + { return m_hServerConn != 0; + } + + __forceinline void writeStr(const char *pszSetting, const JSONNode &node) + { + CMStringW str(node.as_mstring()); + if (!str.IsEmpty()) + setWString(pszSetting, str); + } + + class CWhatsAppQRDlg *m_pQRDlg; + + // PROTO_INTERFACE ///////////////////////////////////////////////////////////////////// + + MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; + INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; + HANDLE SearchBasic(const wchar_t* id) override; + int SendMsg(MCONTACT hContact, int flags, const char* msg) override; + int SetStatus(int iNewStatus) override; + int UserIsTyping(MCONTACT hContact, int type) override; + + void OnErase() override; + void OnModulesLoaded() override; + + // Services //////////////////////////////////////////////////////////////////////////// + + INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); + + // Events ////////////////////////////////////////////////////////////////////////////// + + int __cdecl OnOptionsInit(WPARAM, LPARAM); + int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); + + // Options ///////////////////////////////////////////////////////////////////////////// + + CMOption m_wszNick; // your nick name in presence + CMOption m_wszDeviceName; // how do you see Miranda in mobile phone + CMOption m_wszDefaultGroup; // clist group to store contacts + CMOption m_bHideGroupchats; // do not open chat windows on creation + CMOption m_bUseBbcodes; // use extended markup for messages + + // Processing Threads ////////////////////////////////////////////////////////////////// + + void __cdecl SearchAckThread(void*); + void __cdecl ServerThread(void*); +}; + +struct CMPlugin : public ACCPROTOPLUGIN +{ + HNETLIBUSER hAvatarUser = nullptr; + HNETLIBCONN hAvatarConn = nullptr; + bool SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai); + + bool bHasMessageState = false; + + CMPlugin(); + + int Load() override; + int Unload() override; +}; + +#endif diff --git a/protocols/WhatsApp/src/server.cpp b/protocols/WhatsApp/src/server.cpp index 7512b05caf..24d64e5aa7 100644 --- a/protocols/WhatsApp/src/server.cpp +++ b/protocols/WhatsApp/src/server.cpp @@ -1,418 +1,418 @@ -/* - -WhatsApp plugin for Miranda NG -Copyright © 2019-23 George Hazan - -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// gateway worker thread - -void WhatsAppProto::ServerThread(void *) -{ - do { - m_bRespawn = m_bUnregister = false; - ServerThreadWorker(); - } - while (m_bRespawn); - - OnLoggedOut(); -} - -void WhatsAppProto::ServerThreadWorker() -{ - // connect websocket - NETLIBHTTPHEADER hdrs[] = - { - { "Origin", "https://web.whatsapp.com" }, - { 0, 0 } - }; - - NLHR_PTR pReply(WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws/chat", hdrs)); - if (pReply == nullptr) { - debugLogA("Server connection failed, exiting"); - return; - } - - if (pReply->resultCode != 101) - return; - - delete m_noise; - m_noise = new WANoise(this); - m_noise->init(); - - debugLogA("Server connection succeeded"); - m_hServerConn = pReply->nlc; - m_lastRecvTime = time(0); - m_iPacketId = 1; - - Utils_GetRandom(m_wMsgPrefix, sizeof(m_wMsgPrefix)); - - Wa__HandshakeMessage__ClientHello client; - client.ephemeral = {m_noise->ephemeral.pub.length(), m_noise->ephemeral.pub.data()}; - client.has_ephemeral = true; - - Wa__HandshakeMessage msg; - msg.clienthello = &client; - WSSend(msg); - - MBinBuffer netbuf; - - for (m_bTerminated = false; !m_bTerminated;) { - unsigned char buf[2048]; - int bufSize = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP); - if (bufSize == 0) { - debugLogA("Gateway connection gracefully closed"); - break; - } - if (bufSize < 0) { - debugLogA("Gateway connection error, exiting"); - break; - } - - netbuf.append(buf, bufSize); - - WSHeader hdr; - if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) - continue; - - // we lack some data, let's read them - if (netbuf.length() < hdr.headerSize + hdr.payloadSize) - if (!WSReadPacket(hdr, netbuf)) - break; - - // debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", - // netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); - // Netlib_Dump(m_hServerConn, netbuf.data(), netbuf.length(), false, 0); - - m_lastRecvTime = time(0); - - // read all payloads from the current buffer, one by one - while (true) { - MBinBuffer currPacket; - currPacket.assign(netbuf.data() + hdr.headerSize, hdr.payloadSize); - - switch (hdr.opCode) { - case 1: // json packet - debugLogA("Text packet, skipping"); - /* - currPacket.append("", 1); // add 0 to use strchr safely - CMStringA szJson(pos, (int)dataSize); - - JSONNode root = JSONNode::parse(szJson); - if (root) { - debugLogA("JSON received:\n%s", start); - - CMStringA szPrefix(start, int(pos - start - 1)); - auto *pReq = m_arPacketQueue.find((WARequest *)&szPrefix); - if (pReq != nullptr) { - root << CHAR_PARAM("$id$", szPrefix); - } - } - } - */ - break; - - case 2: // binary packet - if (hdr.payloadSize > 32) - ProcessBinaryPacket(currPacket.data(), hdr.payloadSize); - break; - - case 8: // close - debugLogA("server required to exit"); - m_bRespawn = m_bTerminated = true; // simply reconnect, don't exit - break; - - default: - Netlib_Dump(m_hServerConn, currPacket.data(), hdr.payloadSize, false, 0); - } - - netbuf.remove(hdr.headerSize + hdr.payloadSize); - // debugLogA("%d bytes removed from network buffer, %d bytes remain", hdr.headerSize + hdr.payloadSize, netbuf.length()); - if (netbuf.length() == 0) - break; - - // if we have not enough data for header, continue reading - if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) { - debugLogA("not enough data for header, continue reading"); - break; - } - - // if we have not enough data for data, continue reading - if (hdr.headerSize + hdr.payloadSize > netbuf.length()) { - debugLogA("not enough place for data (%d+%d > %d), continue reading", hdr.headerSize, hdr.payloadSize, netbuf.length()); - break; - } - - debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", - netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); - } - } - - debugLogA("Server connection dropped"); - Netlib_CloseHandle(m_hServerConn); - m_hServerConn = nullptr; -} - -bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res) -{ - size_t currPacketSize = res.length() - hdr.headerSize; - - char buf[1024]; - while (currPacketSize < hdr.payloadSize) { - int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP); - if (result == 0) { - debugLogA("Gateway connection gracefully closed"); - return false; - } - if (result < 0) { - debugLogA("Gateway connection error, exiting"); - return false; - } - - currPacketSize += result; - res.append(buf, result); - } - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Binary data processing - -void WhatsAppProto::ProcessBinaryPacket(const uint8_t *pData, size_t cbDataLen) -{ - while (size_t payloadLen = m_noise->decodeFrame(pData, cbDataLen)) { - if (m_noise->bInitFinished) { - MBinBuffer buf = m_noise->decrypt(pData, payloadLen); - - WAReader rdr(buf.data(), buf.length()); - auto b = rdr.readInt8(); - if (b & 2) { - buf.remove(1); - buf = unzip(buf); - rdr = WAReader(buf.data(), buf.length()); - } - - if (WANode *pNode = rdr.readNode()) { - CMStringA szText; - pNode->print(szText); - debugLogA("Got binary node:\n%s", szText.c_str()); - - auto pHandler = FindPersistentHandler(*pNode); - if (pHandler) - (this->*pHandler)(*pNode); - else - debugLogA("cannot handle incoming message"); - - delete pNode; - } - else { - debugLogA("wrong or broken payload"); - Netlib_Dump(m_hServerConn, pData, cbDataLen, false, 0); - } - } - else OnProcessHandshake(pData, (int)payloadLen); - - pData = (BYTE*)pData + payloadLen; - cbDataLen -= payloadLen; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ProcessFailure(int code) -{ - switch (code) { - case 401: - debugLogA("Connection logged out from another device, exiting"); - Popup(0, TranslateT("This account was logged out from mobile phone, you need to link it again"), m_tszUserName); - - OnErase(); - break; - - case 408: - debugLogA("Connection lost, exiting"); - break; - - case 411: - debugLogA("Conflict between two devices, exiting"); - break; - - case 428: - debugLogA("Connection forcibly closed by the server, exiting"); - break; - - case 440: - debugLogA("Connection replaced from another device, exiting"); - break; - - case 515: - debugLogA("Server required to restart immediately, leaving thread"); - m_bRespawn = true; - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnLoggedIn() -{ - debugLogA("WhatsAppProto::OnLoggedIn"); - - if (m_bUnregister) { - SendUnregister(); - m_bTerminated = true; - return; - } - - SetServerStatus(m_iDesiredStatus); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); - m_iStatus = m_iDesiredStatus; - m_bUpdatedPrekeys = false; - - m_impl.m_keepAlive.Start(1000); - - // retrieve initial info - WANodeIq abt(IQ::GET, "abt"); - abt.addChild("props")->addAttr("protocol", "1"); - WSSendNode(abt, &WhatsAppProto::OnIqDoNothing); - - WSSendNode( - WANodeIq(IQ::GET, "w") << XCHILD("props"), - &WhatsAppProto::OnIqDoNothing); - - WSSendNode( - WANodeIq(IQ::GET, "blocklist"), - &WhatsAppProto::OnIqBlockList); - - WSSendNode( - WANodeIq(IQ::GET, "privacy") << XCHILD("privacy"), - &WhatsAppProto::OnIqDoNothing); - - GC_RefreshMetadata(); -} - -void WhatsAppProto::OnLoggedOut(void) -{ - m_impl.m_keepAlive.Stop(); - - debugLogA("WhatsAppProto::OnLoggedOut"); - m_bTerminated = true; - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - setAllContactStatuses(ID_STATUS_OFFLINE, false); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Service packets sending - -void WhatsAppProto::SendAck(const WANode &node) -{ - WANode ack("ack"); - ack << CHAR_PARAM("to", node.getAttr("from")) << CHAR_PARAM("id", node.getAttr("id")) << CHAR_PARAM("class", node.title); - if (node.title != "message") - if (auto *param = node.getAttr("type")) - ack << CHAR_PARAM("type", param); - if (auto *param = node.getAttr("participant")) - ack << CHAR_PARAM("participant", param); - if (auto *param = node.getAttr("recipient")) - ack << CHAR_PARAM("recipient", param); - WSSendNode(ack); -} - -void WhatsAppProto::SendKeepAlive() -{ - time_t now = time(0); - if (now - m_lastRecvTime > 20) { - WSSendNode(WANodeIq(IQ::GET, "w:p") << XCHILD("ping"), &WhatsAppProto::OnIqDoNothing); - - m_lastRecvTime = now; - } - - for (auto &it : m_arUsers) { - if (it->m_timer1 && now - it->m_timer1 > 600) { - it->m_timer1 = 0; - it->m_timer2 = now; - setWord(it->hContact, "Status", ID_STATUS_AWAY); - } - else if (it->m_timer2 && now - it->m_timer2 > 600) { - it->m_timer2 = 0; - setWord(it->hContact, "Status", ID_STATUS_OFFLINE); - } - } -} - -void WhatsAppProto::SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType) -{ - WANode receipt("receipt"); - receipt << CHAR_PARAM("id", pszId); - - if (!mir_strcmp(pszType, "read") || !mir_strcmp(pszType, "read-self")) - receipt << INT_PARAM("t", time(0)); - - if (!mir_strcmp(pszType, "sender") && WAJid(pszTo).isUser()) - receipt << CHAR_PARAM("to", pszParticipant) << CHAR_PARAM("recipient", pszTo); - else { - receipt << CHAR_PARAM("to", pszTo); - if (pszParticipant) - receipt << CHAR_PARAM("participant", pszParticipant); - } - - if (pszType) - receipt << CHAR_PARAM("type", pszType); - WSSendNode(receipt); -} - -void WhatsAppProto::SetServerStatus(int iStatus) -{ - if (mir_wstrlen(m_wszNick)) - WSSendNode( - WANode("presence") << CHAR_PARAM("name", T2Utf(m_wszNick)) << CHAR_PARAM("type", (iStatus == ID_STATUS_ONLINE) ? "available" : "unavailable"), - &WhatsAppProto::OnIqDoNothing); -} - -void WhatsAppProto::SendUnregister() -{ - WANodeIq iq(IQ::SET, "md"); - *iq.addChild("remove-companion-device") << CHAR_PARAM("jid", WAJid(m_szJid, getDword(DBKEY_DEVICE_ID)).toString()) << CHAR_PARAM("reason", "user's decision"); - WSSendNode(iq, &WhatsAppProto::OnIqDoNothing); - - m_bTerminated = true; -} - -void WhatsAppProto::SendUsync(const LIST &jids, void *pUserInfo) -{ - WANodeIq iq(IQ::GET, "usync"); - - auto *pNode1 = iq.addChild("usync"); - *pNode1 << CHAR_PARAM("sid", GenerateMessageId()) << CHAR_PARAM("mode", "query") << CHAR_PARAM("last", "true") - << CHAR_PARAM("index", "0") << CHAR_PARAM("context", "message"); - - pNode1->addChild("query")->addChild("devices")->addAttr("version", "2"); - auto *pList = pNode1->addChild("list"); - for (auto &it : jids) - pList->addChild("user")->addAttr("jid", it); - - WSSendNode(iq, &WhatsAppProto::OnIqGetUsync, pUserInfo); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ShutdownSession() -{ - if (m_bTerminated) - return; - - debugLogA("WhatsAppProto::ShutdownSession"); - - // shutdown all resources - if (m_hServerConn) - Netlib_Shutdown(m_hServerConn); - - OnLoggedOut(); -} +/* + +WhatsApp plugin for Miranda NG +Copyright © 2019-23 George Hazan + +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread + +void WhatsAppProto::ServerThread(void *) +{ + do { + m_bRespawn = m_bUnregister = false; + ServerThreadWorker(); + } + while (m_bRespawn); + + OnLoggedOut(); +} + +void WhatsAppProto::ServerThreadWorker() +{ + // connect websocket + NETLIBHTTPHEADER hdrs[] = + { + { "Origin", "https://web.whatsapp.com" }, + { 0, 0 } + }; + + NLHR_PTR pReply(WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws/chat", hdrs)); + if (pReply == nullptr) { + debugLogA("Server connection failed, exiting"); + return; + } + + if (pReply->resultCode != 101) + return; + + delete m_noise; + m_noise = new WANoise(this); + m_noise->init(); + + debugLogA("Server connection succeeded"); + m_hServerConn = pReply->nlc; + m_lastRecvTime = time(0); + m_iPacketId = 1; + + Utils_GetRandom(m_wMsgPrefix, sizeof(m_wMsgPrefix)); + + Wa__HandshakeMessage__ClientHello client; + client.ephemeral = {m_noise->ephemeral.pub.length(), m_noise->ephemeral.pub.data()}; + client.has_ephemeral = true; + + Wa__HandshakeMessage msg; + msg.clienthello = &client; + WSSend(msg); + + MBinBuffer netbuf; + + for (m_bTerminated = false; !m_bTerminated;) { + unsigned char buf[2048]; + int bufSize = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + + netbuf.append(buf, bufSize); + + WSHeader hdr; + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) + continue; + + // we lack some data, let's read them + if (netbuf.length() < hdr.headerSize + hdr.payloadSize) + if (!WSReadPacket(hdr, netbuf)) + break; + + // debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", + // netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + // Netlib_Dump(m_hServerConn, netbuf.data(), netbuf.length(), false, 0); + + m_lastRecvTime = time(0); + + // read all payloads from the current buffer, one by one + while (true) { + MBinBuffer currPacket; + currPacket.assign(netbuf.data() + hdr.headerSize, hdr.payloadSize); + + switch (hdr.opCode) { + case 1: // json packet + debugLogA("Text packet, skipping"); + /* + currPacket.append("", 1); // add 0 to use strchr safely + CMStringA szJson(pos, (int)dataSize); + + JSONNode root = JSONNode::parse(szJson); + if (root) { + debugLogA("JSON received:\n%s", start); + + CMStringA szPrefix(start, int(pos - start - 1)); + auto *pReq = m_arPacketQueue.find((WARequest *)&szPrefix); + if (pReq != nullptr) { + root << CHAR_PARAM("$id$", szPrefix); + } + } + } + */ + break; + + case 2: // binary packet + if (hdr.payloadSize > 32) + ProcessBinaryPacket(currPacket.data(), hdr.payloadSize); + break; + + case 8: // close + debugLogA("server required to exit"); + m_bRespawn = m_bTerminated = true; // simply reconnect, don't exit + break; + + default: + Netlib_Dump(m_hServerConn, currPacket.data(), hdr.payloadSize, false, 0); + } + + netbuf.remove(hdr.headerSize + hdr.payloadSize); + // debugLogA("%d bytes removed from network buffer, %d bytes remain", hdr.headerSize + hdr.payloadSize, netbuf.length()); + if (netbuf.length() == 0) + break; + + // if we have not enough data for header, continue reading + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) { + debugLogA("not enough data for header, continue reading"); + break; + } + + // if we have not enough data for data, continue reading + if (hdr.headerSize + hdr.payloadSize > netbuf.length()) { + debugLogA("not enough place for data (%d+%d > %d), continue reading", hdr.headerSize, hdr.payloadSize, netbuf.length()); + break; + } + + debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", + netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + } + } + + debugLogA("Server connection dropped"); + Netlib_CloseHandle(m_hServerConn); + m_hServerConn = nullptr; +} + +bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res) +{ + size_t currPacketSize = res.length() - hdr.headerSize; + + char buf[1024]; + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + return false; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + return false; + } + + currPacketSize += result; + res.append(buf, result); + } + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Binary data processing + +void WhatsAppProto::ProcessBinaryPacket(const uint8_t *pData, size_t cbDataLen) +{ + while (size_t payloadLen = m_noise->decodeFrame(pData, cbDataLen)) { + if (m_noise->bInitFinished) { + MBinBuffer buf = m_noise->decrypt(pData, payloadLen); + + WAReader rdr(buf.data(), buf.length()); + auto b = rdr.readInt8(); + if (b & 2) { + buf.remove(1); + buf = unzip(buf); + rdr = WAReader(buf.data(), buf.length()); + } + + if (WANode *pNode = rdr.readNode()) { + CMStringA szText; + pNode->print(szText); + debugLogA("Got binary node:\n%s", szText.c_str()); + + auto pHandler = FindPersistentHandler(*pNode); + if (pHandler) + (this->*pHandler)(*pNode); + else + debugLogA("cannot handle incoming message"); + + delete pNode; + } + else { + debugLogA("wrong or broken payload"); + Netlib_Dump(m_hServerConn, pData, cbDataLen, false, 0); + } + } + else OnProcessHandshake(pData, (int)payloadLen); + + pData = (BYTE*)pData + payloadLen; + cbDataLen -= payloadLen; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::ProcessFailure(int code) +{ + switch (code) { + case 401: + debugLogA("Connection logged out from another device, exiting"); + Popup(0, TranslateT("This account was logged out from mobile phone, you need to link it again"), m_tszUserName); + + OnErase(); + break; + + case 408: + debugLogA("Connection lost, exiting"); + break; + + case 411: + debugLogA("Conflict between two devices, exiting"); + break; + + case 428: + debugLogA("Connection forcibly closed by the server, exiting"); + break; + + case 440: + debugLogA("Connection replaced from another device, exiting"); + break; + + case 515: + debugLogA("Server required to restart immediately, leaving thread"); + m_bRespawn = true; + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnLoggedIn() +{ + debugLogA("WhatsAppProto::OnLoggedIn"); + + if (m_bUnregister) { + SendUnregister(); + m_bTerminated = true; + return; + } + + SetServerStatus(m_iDesiredStatus); + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); + m_iStatus = m_iDesiredStatus; + m_bUpdatedPrekeys = false; + + m_impl.m_keepAlive.Start(1000); + + // retrieve initial info + WANodeIq abt(IQ::GET, "abt"); + abt.addChild("props")->addAttr("protocol", "1"); + WSSendNode(abt, &WhatsAppProto::OnIqDoNothing); + + WSSendNode( + WANodeIq(IQ::GET, "w") << XCHILD("props"), + &WhatsAppProto::OnIqDoNothing); + + WSSendNode( + WANodeIq(IQ::GET, "blocklist"), + &WhatsAppProto::OnIqBlockList); + + WSSendNode( + WANodeIq(IQ::GET, "privacy") << XCHILD("privacy"), + &WhatsAppProto::OnIqDoNothing); + + GC_RefreshMetadata(); +} + +void WhatsAppProto::OnLoggedOut(void) +{ + m_impl.m_keepAlive.Stop(); + + debugLogA("WhatsAppProto::OnLoggedOut"); + m_bTerminated = true; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Service packets sending + +void WhatsAppProto::SendAck(const WANode &node) +{ + WANode ack("ack"); + ack << CHAR_PARAM("to", node.getAttr("from")) << CHAR_PARAM("id", node.getAttr("id")) << CHAR_PARAM("class", node.title); + if (node.title != "message") + if (auto *param = node.getAttr("type")) + ack << CHAR_PARAM("type", param); + if (auto *param = node.getAttr("participant")) + ack << CHAR_PARAM("participant", param); + if (auto *param = node.getAttr("recipient")) + ack << CHAR_PARAM("recipient", param); + WSSendNode(ack); +} + +void WhatsAppProto::SendKeepAlive() +{ + time_t now = time(0); + if (now - m_lastRecvTime > 20) { + WSSendNode(WANodeIq(IQ::GET, "w:p") << XCHILD("ping"), &WhatsAppProto::OnIqDoNothing); + + m_lastRecvTime = now; + } + + for (auto &it : m_arUsers) { + if (it->m_timer1 && now - it->m_timer1 > 600) { + it->m_timer1 = 0; + it->m_timer2 = now; + setWord(it->hContact, "Status", ID_STATUS_AWAY); + } + else if (it->m_timer2 && now - it->m_timer2 > 600) { + it->m_timer2 = 0; + setWord(it->hContact, "Status", ID_STATUS_OFFLINE); + } + } +} + +void WhatsAppProto::SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType) +{ + WANode receipt("receipt"); + receipt << CHAR_PARAM("id", pszId); + + if (!mir_strcmp(pszType, "read") || !mir_strcmp(pszType, "read-self")) + receipt << INT_PARAM("t", time(0)); + + if (!mir_strcmp(pszType, "sender") && WAJid(pszTo).isUser()) + receipt << CHAR_PARAM("to", pszParticipant) << CHAR_PARAM("recipient", pszTo); + else { + receipt << CHAR_PARAM("to", pszTo); + if (pszParticipant) + receipt << CHAR_PARAM("participant", pszParticipant); + } + + if (pszType) + receipt << CHAR_PARAM("type", pszType); + WSSendNode(receipt); +} + +void WhatsAppProto::SetServerStatus(int iStatus) +{ + if (mir_wstrlen(m_wszNick)) + WSSendNode( + WANode("presence") << CHAR_PARAM("name", T2Utf(m_wszNick)) << CHAR_PARAM("type", (iStatus == ID_STATUS_ONLINE) ? "available" : "unavailable"), + &WhatsAppProto::OnIqDoNothing); +} + +void WhatsAppProto::SendUnregister() +{ + WANodeIq iq(IQ::SET, "md"); + *iq.addChild("remove-companion-device") << CHAR_PARAM("jid", WAJid(m_szJid, getDword(DBKEY_DEVICE_ID)).toString()) << CHAR_PARAM("reason", "user's decision"); + WSSendNode(iq, &WhatsAppProto::OnIqDoNothing); + + m_bTerminated = true; +} + +void WhatsAppProto::SendUsync(const LIST &jids, void *pUserInfo) +{ + WANodeIq iq(IQ::GET, "usync"); + + auto *pNode1 = iq.addChild("usync"); + *pNode1 << CHAR_PARAM("sid", GenerateMessageId()) << CHAR_PARAM("mode", "query") << CHAR_PARAM("last", "true") + << CHAR_PARAM("index", "0") << CHAR_PARAM("context", "message"); + + pNode1->addChild("query")->addChild("devices")->addAttr("version", "2"); + auto *pList = pNode1->addChild("list"); + for (auto &it : jids) + pList->addChild("user")->addAttr("jid", it); + + WSSendNode(iq, &WhatsAppProto::OnIqGetUsync, pUserInfo); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("WhatsAppProto::ShutdownSession"); + + // shutdown all resources + if (m_hServerConn) + Netlib_Shutdown(m_hServerConn); + + OnLoggedOut(); +} diff --git a/protocols/YAMN/YAMN.vcxproj b/protocols/YAMN/YAMN.vcxproj index 213c4df7c1..203ab9c98b 100644 --- a/protocols/YAMN/YAMN.vcxproj +++ b/protocols/YAMN/YAMN.vcxproj @@ -1,97 +1,97 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - YAMN - {C5A87409-F08C-4A07-A8F9-1F5D52BA6D72} - - - - - - - - ..\stdafx.h - - - ..\stdafx.h - - - - - ..\stdafx.h - - - ..\stdafx.h - - - ..\stdafx.h - - - - - ..\stdafx.h - - - ..\..\stdafx.h - - - ..\..\stdafx.h - - - ..\..\stdafx.h - - - - Create - - - - - - - - - - - - - - - - - Sync - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + YAMN + {C5A87409-F08C-4A07-A8F9-1F5D52BA6D72} + + + + + + + + ..\stdafx.h + + + ..\stdafx.h + + + + + ..\stdafx.h + + + ..\stdafx.h + + + ..\stdafx.h + + + + + ..\stdafx.h + + + ..\..\stdafx.h + + + ..\..\stdafx.h + + + ..\..\stdafx.h + + + + Create + + + + + + + + + + + + + + + + + Sync + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/YAMN/YAMN.vcxproj.filters b/protocols/YAMN/YAMN.vcxproj.filters index eed1e0e317..0f2b3632b4 100644 --- a/protocols/YAMN/YAMN.vcxproj.filters +++ b/protocols/YAMN/YAMN.vcxproj.filters @@ -1,120 +1,120 @@ - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - Resource Files - - - - - Resource Files - - - Resource Files - - - Resource Files - - - Resource Files - - + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + \ No newline at end of file diff --git a/protocols/YAMN/res/YAMN.rc b/protocols/YAMN/res/YAMN.rc index 1268b0dc8a..de075e62a2 100644 --- a/protocols/YAMN/res/YAMN.rc +++ b/protocols/YAMN/res/YAMN.rc @@ -1,305 +1,305 @@ -// 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 - -///////////////////////////////////////////////////////////////////////////// -// Neutral resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -#pragma code_page(1252) - -#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 - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_DLGVIEWMESSAGES, DIALOG - BEGIN - LEFTMARGIN, 5 - RIGHTMARGIN, 455 - TOPMARGIN, 5 - BOTTOMMARGIN, 105 - END - - IDD_DLGBADCONNECT, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 179 - TOPMARGIN, 7 - BOTTOMMARGIN, 43 - END - - IDD_POP3ACCOUNTOPT, DIALOG - BEGIN - VERTGUIDE, 155 - VERTGUIDE, 236 - END - - IDD_CHOOSESTATUSMODES, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 219 - TOPMARGIN, 7 - BOTTOMMARGIN, 147 - END - - IDD_YAMNOPT, DIALOG - BEGIN - RIGHTMARGIN, 310 - VERTGUIDE, 8 - END - - IDD_POP3ACCOUNTPOPUP, DIALOG - BEGIN - VERTGUIDE, 155 - VERTGUIDE, 236 - END -END -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_DLGVIEWMESSAGES DIALOG 50, 200, 460, 110 -STYLE DS_SETFONT | DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME -FONT 8, "MS Shell Dlg" -BEGIN - CONTROL "List4",IDC_LISTMAILS,"SysListView32",LVS_REPORT | LVS_EDITLABELS | WS_BORDER | WS_TABSTOP,5,5,450,70 - DEFPUSHBUTTON "",IDC_BTNOK,395,90,60,15 - PUSHBUTTON "",IDC_BTNAPP,263,90,114,15 - PUSHBUTTON "",IDC_BTNDEL,5,90,114,15 - LTEXT "",IDC_STSTATUS,5,75,450,10 - PUSHBUTTON "",IDC_BTNCHECKALL,150,91,92,14 -END - -IDD_DLGSHOWMESSAGE DIALOGEX 50, 200, 460, 132 -STYLE DS_SETFONT | DS_3DLOOK | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - CONTROL "List5",IDC_LISTHEADERS,"SysListView32",LVS_REPORT | LVS_EDITLABELS | WS_BORDER | WS_TABSTOP,5,5,450,70 - CONTROL "",IDC_SPLITTER,"Static",SS_ENHMETAFILE | WS_TABSTOP,0,80,187,2,WS_EX_STATICEDGE - EDITTEXT IDC_EDITBODY,3,84,454,45,ES_MULTILINE | ES_READONLY | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL -END - -IDD_DLGBADCONNECT DIALOG 0, 0, 186, 76 -STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK",IDC_BTNOK,69,55,50,14 - LTEXT "",IDC_STATICMSG,7,7,172,37 -END - -IDD_POP3ACCOUNTOPT DIALOGEX 0, 0, 310, 230 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - COMBOBOX IDC_COMBOACCOUNT,4,6,106,65,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "+",IDC_BTNADD,118,6,15,13 - PUSHBUTTON "-",IDC_BTNDEL,140,6,15,13 - GROUPBOX "Account",IDC_STATIC,4,22,151,120 - LTEXT "Name:",IDC_STATIC,10,34,44,10 - EDITTEXT IDC_EDITNAME,56,32,92,12,ES_AUTOHSCROLL - LTEXT "Server:",IDC_STATIC,10,50,44,8 - EDITTEXT IDC_EDITSERVER,56,48,92,12,ES_AUTOHSCROLL | WS_GROUP - LTEXT "Port:",IDC_STATIC,10,65,44,8,SS_CENTERIMAGE - EDITTEXT IDC_EDITPORT,57,64,27,12,ES_AUTOHSCROLL | ES_NUMBER | WS_GROUP - CONTROL "SSL",IDC_CHECKSSL,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,87,66,27,10 - CONTROL "APOP",IDC_CHECKAPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,66,34,10 - LTEXT "User Name:",IDC_STATIC,10,82,44,8 - EDITTEXT IDC_EDITLOGIN,57,80,92,12,ES_AUTOHSCROLL | WS_GROUP - LTEXT "Password:",IDC_STATIC,10,96,44,8 - EDITTEXT IDC_EDITPASS,57,94,92,12,ES_PASSWORD | ES_AUTOHSCROLL | WS_GROUP - LTEXT "Codepage:",IDC_STATIC,10,111,44,8 - COMBOBOX IDC_COMBOCP,57,108,92,130,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "Default",IDC_BTNDEFAULT,9,124,54,13 - CONTROL "Disable STLS",IDC_CHECKNOTLS,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,83,125,69,10 - LTEXT "Status:",IDC_STATIC,164,2,80,8 - LTEXT "",IDC_STSTATUS,164,13,143,8,SS_CENTERIMAGE - GROUPBOX "Options",IDC_STATIC,161,22,147,120 - CONTROL "Check this account",IDC_CHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,167,32,118,10,WS_EX_TRANSPARENT - CONTROL "Startup check",IDC_CHECKSTART,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,167,43,78,10 - LTEXT "Check interval [min]:",IDC_STATIC,168,56,94,8 - EDITTEXT IDC_EDITINTERVAL,259,53,20,12,ES_AUTOHSCROLL | ES_NUMBER,WS_EX_TRANSPARENT - PUSHBUTTON "Only check when...",IDC_BTNSTATUS,195,69,81,13 - CONTROL "Auto retrieve body",IDC_AUTOBODY,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,166,86,137,10 - CONTROL "Check from menu",IDC_CHECKFORCE,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,166,97,137,8 - CONTROL "Use contact notification for this account",IDC_CHECKCONTACT, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,107,138,10,WS_EX_TRANSPARENT - CONTROL "Replace nickname",IDC_CHECKCONTACTNICK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,187,117,117,10,WS_EX_TRANSPARENT - CONTROL "Disable Events",IDC_CHECKCONTACTNOEVENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,187,128,115,10,WS_EX_TRANSPARENT - GROUPBOX "Notifications",IDC_GBNEWMAIL,4,143,304,87 - GROUPBOX "New Mail",IDC_STATIC,7,153,149,73 - CONTROL "Sound",IDC_CHECKSND,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,12,162,60,10 - CONTROL "Message",IDC_CHECKMSG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,174,135,10 - CONTROL "Keyboard Flash",IDC_CHECKKBN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,186,132,9 - CONTROL "Tray Icon",IDC_CHECKICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,85,163,65,10 - CONTROL "Execute Application",IDC_CHECKAPP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,198,135,8 - PUSHBUTTON "...",IDC_BTNAPP,19,209,16,12 - EDITTEXT IDC_EDITAPP,41,209,65,12,ES_AUTOHSCROLL - EDITTEXT IDC_EDITAPPPARAM,111,209,40,12,ES_AUTOHSCROLL - GROUPBOX "Errors",IDC_STATIC,161,153,143,44 - CONTROL "Sound notification if failed",IDC_CHECKFSND,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,163,135,10 - CONTROL "Message notification if failed",IDC_CHECKFMSG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,174,135,10 - CONTROL "Tray icon notification if failed",IDC_CHECKFICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,186,135,10 - PUSHBUTTON "Reset counter",IDC_BTNRESET,161,200,75,13 - LTEXT "",IDC_STTIMELEFT,163,216,141,8 -END - -IDD_CHOOSESTATUSMODES DIALOG 0, 0, 226, 154 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Check while..." -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK",IDOK,112,133,50,14 - PUSHBUTTON "Cancel",IDCANCEL,169,133,50,14 - GROUPBOX "Choose modes",IDC_STATUSGROUP,7,7,212,119 - CONTROL "Offline",IDC_CHECKST0,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,19,70,9 - CONTROL "Online",IDC_CHECKST1,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,39,70,9 - CONTROL "Away",IDC_CHECKST2,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,62,70,9 - CONTROL "Not available",IDC_CHECKST3,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,83,70,9 - CONTROL "Occupied",IDC_CHECKST4,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,104,70,9 - CONTROL "Do not disturb",IDC_CHECKST5,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,19,70,9 - CONTROL "Free for chat",IDC_CHECKST6,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,39,70,9 - CONTROL "Invisible",IDC_CHECKST7,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,62,70,9 -END - -IDD_YAMNOPT DIALOGEX 0, 0, 312, 121 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - GROUPBOX "YAMN General Options",IDC_STATIC,3,2,303,45 - CONTROL "TopToolBar button ""Check mail""",IDC_CHECKTTB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,15,294,11 - GROUPBOX "MailBrowser Options",IDC_STATIC,3,48,151,68 - CONTROL "Enable Close on Delete Button",IDC_CLOSEONDELETE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,60,143,11 - CONTROL "Show long localized date",IDC_LONGDATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,65,129,8 - CONTROL "Don't show today's date",IDC_SMARTDATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,80,129,8 - GROUPBOX "Date/Time Representation",IDC_STATIC,159,48,148,68 - CONTROL "Don't show seconds",IDC_NOSECONDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,94,129,8 -END - -IDD_POP3ACCOUNTPOPUP DIALOGEX 0, 0, 315, 230 -STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_VISIBLE -EXSTYLE WS_EX_CONTROLPARENT -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - COMBOBOX IDC_COMBOACCOUNT,4,4,140,65,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP - GROUPBOX "Mail Notifications",IDC_GBNEWMAIL,5,23,300,76 - CONTROL "Popup",IDC_CHECKPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,32,108,10 - CONTROL "Single popup",IDC_RADIOPOP1,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,23,43,95,10 - CONTROL "Multi popup",IDC_RADIOPOPN,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,23,55,95,10 - CONTROL "Use custom color",IDC_CHECKCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,83,107,10 - CONTROL "",IDC_CPB,"ColourPicker",WS_TABSTOP,145,66,29,12 - CONTROL "",IDC_CPT,"ColourPicker",WS_TABSTOP,145,83,29,12 - EDITTEXT IDC_EDITPOPS,23,65,20,12,ES_AUTOHSCROLL - GROUPBOX "No new mail notifications",IDC_GBNONEWMAIL,5,152,300,62 - CONTROL "Popup if no mail",IDC_CHECKNPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,161,94,10 - CONTROL "Persistent message",IDC_CHECKNMSGP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,188,110,10 - CONTROL "Use custom color",IDC_CHECKNCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,201,107,10 - CONTROL "",IDC_CPNB,"ColourPicker",WS_TABSTOP,145,181,29,12 - CONTROL "",IDC_CPNT,"ColourPicker",WS_TABSTOP,145,198,29,12 - EDITTEXT IDC_EDITNPOPS,23,173,20,12,ES_AUTOHSCROLL - GROUPBOX "Connection failure notifications",IDC_GBBADCONNECT,5,101,300,49 - CONTROL "Popup notification if failed",IDC_CHECKFPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,110,118,10 - CONTROL "Use custom color",IDC_CHECKFCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,136,95,10 - CONTROL "",IDC_CPFB,"ColourPicker",WS_TABSTOP,145,118,29,12 - CONTROL "",IDC_CPFT,"ColourPicker",WS_TABSTOP,145,134,29,12 - EDITTEXT IDC_EDITFPOPS,23,121,20,12,ES_AUTOHSCROLL - LTEXT "..s Popup duration",IDC_STATIC,45,67,70,8 - LTEXT "..s Popup duration",IDC_STATIC,45,176,70,8 - LTEXT "..s Popup duration",IDC_STATIC,45,122,70,8 - PUSHBUTTON "Preview",IDC_PREVIEW,255,215,49,13 - LTEXT "Background color",IDC_STATIC,177,184,108,10 - LTEXT "Text color",IDC_STATIC,177,200,107,10 - LTEXT "Background color",IDC_STATIC,177,120,108,10 - LTEXT "Text color",IDC_STATIC,177,136,107,10 - LTEXT "Background color",IDC_STATIC,177,69,108,10 - LTEXT "Text color",IDC_STATIC,177,85,107,10 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_CHECKMAIL ICON "checkmail.ico" - -IDI_LAUNCHAPP ICON "launchapp.ico" - -IDI_BADCONNECT ICON "badconnect.ico" - -IDI_NEWMAIL ICON "newmail.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_YAMNOPT AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -#endif // Neutral resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// 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 + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) + +#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 + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DLGVIEWMESSAGES, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 455 + TOPMARGIN, 5 + BOTTOMMARGIN, 105 + END + + IDD_DLGBADCONNECT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_POP3ACCOUNTOPT, DIALOG + BEGIN + VERTGUIDE, 155 + VERTGUIDE, 236 + END + + IDD_CHOOSESTATUSMODES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 219 + TOPMARGIN, 7 + BOTTOMMARGIN, 147 + END + + IDD_YAMNOPT, DIALOG + BEGIN + RIGHTMARGIN, 310 + VERTGUIDE, 8 + END + + IDD_POP3ACCOUNTPOPUP, DIALOG + BEGIN + VERTGUIDE, 155 + VERTGUIDE, 236 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DLGVIEWMESSAGES DIALOG 50, 200, 460, 110 +STYLE DS_SETFONT | DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +FONT 8, "MS Shell Dlg" +BEGIN + CONTROL "List4",IDC_LISTMAILS,"SysListView32",LVS_REPORT | LVS_EDITLABELS | WS_BORDER | WS_TABSTOP,5,5,450,70 + DEFPUSHBUTTON "",IDC_BTNOK,395,90,60,15 + PUSHBUTTON "",IDC_BTNAPP,263,90,114,15 + PUSHBUTTON "",IDC_BTNDEL,5,90,114,15 + LTEXT "",IDC_STSTATUS,5,75,450,10 + PUSHBUTTON "",IDC_BTNCHECKALL,150,91,92,14 +END + +IDD_DLGSHOWMESSAGE DIALOGEX 50, 200, 460, 132 +STYLE DS_SETFONT | DS_3DLOOK | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + CONTROL "List5",IDC_LISTHEADERS,"SysListView32",LVS_REPORT | LVS_EDITLABELS | WS_BORDER | WS_TABSTOP,5,5,450,70 + CONTROL "",IDC_SPLITTER,"Static",SS_ENHMETAFILE | WS_TABSTOP,0,80,187,2,WS_EX_STATICEDGE + EDITTEXT IDC_EDITBODY,3,84,454,45,ES_MULTILINE | ES_READONLY | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL +END + +IDD_DLGBADCONNECT DIALOG 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK",IDC_BTNOK,69,55,50,14 + LTEXT "",IDC_STATICMSG,7,7,172,37 +END + +IDD_POP3ACCOUNTOPT DIALOGEX 0, 0, 310, 230 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + COMBOBOX IDC_COMBOACCOUNT,4,6,106,65,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_BTNADD,118,6,15,13 + PUSHBUTTON "-",IDC_BTNDEL,140,6,15,13 + GROUPBOX "Account",IDC_STATIC,4,22,151,120 + LTEXT "Name:",IDC_STATIC,10,34,44,10 + EDITTEXT IDC_EDITNAME,56,32,92,12,ES_AUTOHSCROLL + LTEXT "Server:",IDC_STATIC,10,50,44,8 + EDITTEXT IDC_EDITSERVER,56,48,92,12,ES_AUTOHSCROLL | WS_GROUP + LTEXT "Port:",IDC_STATIC,10,65,44,8,SS_CENTERIMAGE + EDITTEXT IDC_EDITPORT,57,64,27,12,ES_AUTOHSCROLL | ES_NUMBER | WS_GROUP + CONTROL "SSL",IDC_CHECKSSL,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,87,66,27,10 + CONTROL "APOP",IDC_CHECKAPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,66,34,10 + LTEXT "User Name:",IDC_STATIC,10,82,44,8 + EDITTEXT IDC_EDITLOGIN,57,80,92,12,ES_AUTOHSCROLL | WS_GROUP + LTEXT "Password:",IDC_STATIC,10,96,44,8 + EDITTEXT IDC_EDITPASS,57,94,92,12,ES_PASSWORD | ES_AUTOHSCROLL | WS_GROUP + LTEXT "Codepage:",IDC_STATIC,10,111,44,8 + COMBOBOX IDC_COMBOCP,57,108,92,130,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Default",IDC_BTNDEFAULT,9,124,54,13 + CONTROL "Disable STLS",IDC_CHECKNOTLS,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,83,125,69,10 + LTEXT "Status:",IDC_STATIC,164,2,80,8 + LTEXT "",IDC_STSTATUS,164,13,143,8,SS_CENTERIMAGE + GROUPBOX "Options",IDC_STATIC,161,22,147,120 + CONTROL "Check this account",IDC_CHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,167,32,118,10,WS_EX_TRANSPARENT + CONTROL "Startup check",IDC_CHECKSTART,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,167,43,78,10 + LTEXT "Check interval [min]:",IDC_STATIC,168,56,94,8 + EDITTEXT IDC_EDITINTERVAL,259,53,20,12,ES_AUTOHSCROLL | ES_NUMBER,WS_EX_TRANSPARENT + PUSHBUTTON "Only check when...",IDC_BTNSTATUS,195,69,81,13 + CONTROL "Auto retrieve body",IDC_AUTOBODY,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,166,86,137,10 + CONTROL "Check from menu",IDC_CHECKFORCE,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,166,97,137,8 + CONTROL "Use contact notification for this account",IDC_CHECKCONTACT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,107,138,10,WS_EX_TRANSPARENT + CONTROL "Replace nickname",IDC_CHECKCONTACTNICK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,187,117,117,10,WS_EX_TRANSPARENT + CONTROL "Disable Events",IDC_CHECKCONTACTNOEVENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,187,128,115,10,WS_EX_TRANSPARENT + GROUPBOX "Notifications",IDC_GBNEWMAIL,4,143,304,87 + GROUPBOX "New Mail",IDC_STATIC,7,153,149,73 + CONTROL "Sound",IDC_CHECKSND,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,12,162,60,10 + CONTROL "Message",IDC_CHECKMSG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,174,135,10 + CONTROL "Keyboard Flash",IDC_CHECKKBN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,186,132,9 + CONTROL "Tray Icon",IDC_CHECKICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,85,163,65,10 + CONTROL "Execute Application",IDC_CHECKAPP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,198,135,8 + PUSHBUTTON "...",IDC_BTNAPP,19,209,16,12 + EDITTEXT IDC_EDITAPP,41,209,65,12,ES_AUTOHSCROLL + EDITTEXT IDC_EDITAPPPARAM,111,209,40,12,ES_AUTOHSCROLL + GROUPBOX "Errors",IDC_STATIC,161,153,143,44 + CONTROL "Sound notification if failed",IDC_CHECKFSND,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,163,135,10 + CONTROL "Message notification if failed",IDC_CHECKFMSG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,174,135,10 + CONTROL "Tray icon notification if failed",IDC_CHECKFICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,166,186,135,10 + PUSHBUTTON "Reset counter",IDC_BTNRESET,161,200,75,13 + LTEXT "",IDC_STTIMELEFT,163,216,141,8 +END + +IDD_CHOOSESTATUSMODES DIALOG 0, 0, 226, 154 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Check while..." +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK",IDOK,112,133,50,14 + PUSHBUTTON "Cancel",IDCANCEL,169,133,50,14 + GROUPBOX "Choose modes",IDC_STATUSGROUP,7,7,212,119 + CONTROL "Offline",IDC_CHECKST0,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,19,70,9 + CONTROL "Online",IDC_CHECKST1,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,39,70,9 + CONTROL "Away",IDC_CHECKST2,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,62,70,9 + CONTROL "Not available",IDC_CHECKST3,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,83,70,9 + CONTROL "Occupied",IDC_CHECKST4,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,15,104,70,9 + CONTROL "Do not disturb",IDC_CHECKST5,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,19,70,9 + CONTROL "Free for chat",IDC_CHECKST6,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,39,70,9 + CONTROL "Invisible",IDC_CHECKST7,"Button",BS_AUTOCHECKBOX | BS_NOTIFY | WS_TABSTOP,134,62,70,9 +END + +IDD_YAMNOPT DIALOGEX 0, 0, 312, 121 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "YAMN General Options",IDC_STATIC,3,2,303,45 + CONTROL "TopToolBar button ""Check mail""",IDC_CHECKTTB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,15,294,11 + GROUPBOX "MailBrowser Options",IDC_STATIC,3,48,151,68 + CONTROL "Enable Close on Delete Button",IDC_CLOSEONDELETE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,60,143,11 + CONTROL "Show long localized date",IDC_LONGDATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,65,129,8 + CONTROL "Don't show today's date",IDC_SMARTDATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,80,129,8 + GROUPBOX "Date/Time Representation",IDC_STATIC,159,48,148,68 + CONTROL "Don't show seconds",IDC_NOSECONDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,94,129,8 +END + +IDD_POP3ACCOUNTPOPUP DIALOGEX 0, 0, 315, 230 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_VISIBLE +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + COMBOBOX IDC_COMBOACCOUNT,4,4,140,65,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Mail Notifications",IDC_GBNEWMAIL,5,23,300,76 + CONTROL "Popup",IDC_CHECKPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,32,108,10 + CONTROL "Single popup",IDC_RADIOPOP1,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,23,43,95,10 + CONTROL "Multi popup",IDC_RADIOPOPN,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,23,55,95,10 + CONTROL "Use custom color",IDC_CHECKCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,83,107,10 + CONTROL "",IDC_CPB,"ColourPicker",WS_TABSTOP,145,66,29,12 + CONTROL "",IDC_CPT,"ColourPicker",WS_TABSTOP,145,83,29,12 + EDITTEXT IDC_EDITPOPS,23,65,20,12,ES_AUTOHSCROLL + GROUPBOX "No new mail notifications",IDC_GBNONEWMAIL,5,152,300,62 + CONTROL "Popup if no mail",IDC_CHECKNPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,161,94,10 + CONTROL "Persistent message",IDC_CHECKNMSGP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,188,110,10 + CONTROL "Use custom color",IDC_CHECKNCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,201,107,10 + CONTROL "",IDC_CPNB,"ColourPicker",WS_TABSTOP,145,181,29,12 + CONTROL "",IDC_CPNT,"ColourPicker",WS_TABSTOP,145,198,29,12 + EDITTEXT IDC_EDITNPOPS,23,173,20,12,ES_AUTOHSCROLL + GROUPBOX "Connection failure notifications",IDC_GBBADCONNECT,5,101,300,49 + CONTROL "Popup notification if failed",IDC_CHECKFPOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,110,118,10 + CONTROL "Use custom color",IDC_CHECKFCOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,136,95,10 + CONTROL "",IDC_CPFB,"ColourPicker",WS_TABSTOP,145,118,29,12 + CONTROL "",IDC_CPFT,"ColourPicker",WS_TABSTOP,145,134,29,12 + EDITTEXT IDC_EDITFPOPS,23,121,20,12,ES_AUTOHSCROLL + LTEXT "..s Popup duration",IDC_STATIC,45,67,70,8 + LTEXT "..s Popup duration",IDC_STATIC,45,176,70,8 + LTEXT "..s Popup duration",IDC_STATIC,45,122,70,8 + PUSHBUTTON "Preview",IDC_PREVIEW,255,215,49,13 + LTEXT "Background color",IDC_STATIC,177,184,108,10 + LTEXT "Text color",IDC_STATIC,177,200,107,10 + LTEXT "Background color",IDC_STATIC,177,120,108,10 + LTEXT "Text color",IDC_STATIC,177,136,107,10 + LTEXT "Background color",IDC_STATIC,177,69,108,10 + LTEXT "Text color",IDC_STATIC,177,85,107,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_CHECKMAIL ICON "checkmail.ico" + +IDI_LAUNCHAPP ICON "launchapp.ico" + +IDI_BADCONNECT ICON "badconnect.ico" + +IDI_NEWMAIL ICON "newmail.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_YAMNOPT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/YAMN/src/account.cpp b/protocols/YAMN/src/account.cpp index 91b57ad544..46b7782ab0 100644 --- a/protocols/YAMN/src/account.cpp +++ b/protocols/YAMN/src/account.cpp @@ -1,1051 +1,1051 @@ -/* - * This code implements manipulation with accounts - * such as reading accounts from file, writing them to file, - * finding account by name etc. - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - - // Account status CS - // When we check some account, thread should change status of account to idle, connecting etc. - // So if we want to read status, we have to successfully write and then read. -static mir_cs csAccountStatusCS; - -// File Writing CS -// When 2 threads want to write to file... -static mir_cs csFileWritingCS; - -struct CExportedFunctions AccountExportedFcn[] = -{ - {YAMN_GETSTATUSID, (void *)GetStatusFcn}, - {YAMN_SETSTATUSID, (void *)SetStatusFcn}, -}; - -struct CExportedServices AccountExportedSvc[] = -{ - {MS_YAMN_CREATEPLUGINACCOUNT, CreatePluginAccountSvc}, - {MS_YAMN_DELETEPLUGINACCOUNT, DeletePluginAccountSvc}, - {MS_YAMN_FINDACCOUNTBYNAME, FindAccountByNameSvc}, - {MS_YAMN_GETNEXTFREEACCOUNT, GetNextFreeAccountSvc}, - {MS_YAMN_DELETEACCOUNT, DeletePluginAccountSvc}, - {MS_YAMN_READACCOUNTS, AddAccountsFromFileSvc}, - {MS_YAMN_WRITEACCOUNTS, WriteAccountsToFileSvc}, -}; - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -INT_PTR CreatePluginAccountSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - uint32_t AccountVersion = (uint32_t)lParam; - - //test if we are going to initialize members of suitable structure (structures of plugin and YAMN must match) - if (AccountVersion != YAMN_ACCOUNTVERSION) - return NULL; - - if (Plugin != nullptr) { - CAccount *NewAccount; - if (Plugin->Fcn->NewAccountFcnPtr != nullptr) - //Let plugin create its own structure, which can be derived from CAccount structure - NewAccount = Plugin->Fcn->NewAccountFcnPtr(Plugin, YAMN_ACCOUNTVERSION); - else - //We suggest plugin uses standard CAccount structure, so we create it - NewAccount = new struct CAccount; - - //If not created successfully - if (NewAccount == nullptr) - return NULL; - - NewAccount->Plugin = Plugin; - //Init every members of structure, used by YAMN - InitAccount(NewAccount); - - return (INT_PTR)NewAccount; - } - return NULL; -} - -INT_PTR DeletePluginAccountSvc(WPARAM wParam, LPARAM) -{ - CAccount *OldAccount = (CAccount *)wParam; - - if (OldAccount->Plugin->Fcn != nullptr) { - // Deinit every members and allocated fields of structure used by YAMN - DeInitAccount(OldAccount); - if (OldAccount->Plugin->Fcn->DeleteAccountFcnPtr != nullptr) { - // Let plugin delete its own CAccount derived structure - OldAccount->Plugin->Fcn->DeleteAccountFcnPtr(OldAccount); - } - else { - delete OldAccount; //consider account as standard YAMN CAccount *and use its own destructor - } - return 1; - } - delete OldAccount; //consider account as standard YAMN CAccount *, not initialized before and use its own destructor - return 1; -} - -int InitAccount(CAccount *Which) -{ - //initialize synchronizing objects - Which->AccountAccessSO = new SWMRG; - SWMRGInitialize(Which->AccountAccessSO, nullptr); - Which->MessagesAccessSO = new SWMRG; - SWMRGInitialize(Which->MessagesAccessSO, nullptr); - Which->UsingThreads = new SCOUNTER; - SWMRGInitialize(Which->MessagesAccessSO, nullptr); - - //zero memory, where timestamps are stored - memset(&Which->LastChecked, 0, sizeof(Which->LastChecked)); - memset(&Which->LastSChecked, 0, sizeof(Which->LastSChecked)); - memset(&Which->LastSynchronised, 0, sizeof(Which->LastSynchronised)); - memset(&Which->LastMail, 0, sizeof(Which->LastMail)); - - Which->Name = nullptr; - Which->Mails = nullptr; - Which->Interval = 0; - Which->Flags = 0; - Which->StatusFlags = YAMN_ACC_ST1 + YAMN_ACC_ST7; - Which->Next = nullptr; - - Which->Server = new struct CServer; - Which->AbleToWork = TRUE; - - return 1; -} - -void DeInitAccount(CAccount *Which) -{ - //delete YAMN allocated fields - if (Which->Name != nullptr) - delete[] Which->Name; - if (Which->Server != nullptr) { - if (Which->Server->Name != nullptr) - delete[] Which->Server->Name; - if (Which->Server->Login != nullptr) - delete[] Which->Server->Login; - if (Which->Server->Passwd != nullptr) - delete[] Which->Server->Passwd; - delete[] Which->Server; - } - - SWMRGDelete(Which->AccountAccessSO); - delete Which->AccountAccessSO; - SWMRGDelete(Which->MessagesAccessSO); - delete Which->MessagesAccessSO; - delete Which->UsingThreads; - DeleteMessagesToEndFcn(Which, (HYAMNMAIL)Which->Mails); -} - -void StopSignalFcn(CAccount *Which) -//set event that we are going to delete account -{ - Which->AbleToWork = FALSE; - //do not use synchronizing objects anymore - //any access to these objects then ends with WAIT_FAILED - SetEvent(Which->AccountAccessSO->hFinishEV); - SetEvent(Which->MessagesAccessSO->hFinishEV); -} - -void CodeDecodeString(char *Dest, BOOL Encrypt) -{ - wchar_t Code = STARTCODEPSW; - - if (Dest == nullptr) - return; - - for (; *Dest != (wchar_t)0; Dest++) { - if (Encrypt) - *Dest = *Dest + Code; - else - *Dest = *Dest - Code; - Code += (wchar_t)ADDCODEPSW; - } -} - -static uint32_t PostFileToMemory(HANDLE File, char **MemFile, char **End) -{ - DWORD FileSize, ReadBytes; - if (!(FileSize = GetFileSize(File, nullptr))) { - CloseHandle(File); - return EACC_FILESIZE; - } - - //allocate space in memory, where we copy the whole file - if (nullptr == (*MemFile = new char[FileSize])) { - CloseHandle(File); - return EACC_ALLOC; - } - - //copy file to memory - if (!ReadFile(File, (LPVOID)*MemFile, FileSize, &ReadBytes, nullptr)) { - CloseHandle(File); - delete[] * MemFile; - return EACC_SYSTEM; - } - CloseHandle(File); - *End = *MemFile + FileSize; - return 0; -} - -uint32_t FileToMemory(wchar_t *FileName, char **MemFile, char **End) -{ - HANDLE hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) - return EACC_SYSTEM; - - return PostFileToMemory(hFile, MemFile, End); -} - -#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) -uint32_t ReadStringFromMemory(char **Parser, wchar_t *End, char **StoreTo, wchar_t *DebugString) -{ - //This is the debug version of ReadStringFromMemory function. This version shows MessageBox where - //read string is displayed - wchar_t *Dest, *Finder; - uint32_t Size; - wchar_t Debug[65536]; - - Finder = *Parser; - while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; - mir_snwprintf(Debug, L"%s: %s,length is %d, remaining %d chars", DebugString, *Parser, Finder - *Parser, End - Finder); - MessageBox(NULL, Debug, L"debug", MB_OK); - if (Finder >= End) - return EACC_FILECOMPATIBILITY; - if (Size = Finder - *Parser) { - if (NULL == (Dest = *StoreTo = new wchar_t[Size + 1])) - return EACC_ALLOC; - for (; *Parser <= Finder; (*Parser)++, Dest++) - *Dest = **Parser; - } - else { - *StoreTo = NULL; - (*Parser)++; - } - return 0; -} -#endif - -uint32_t ReadStringFromMemory(char **Parser, char *End, char **StoreTo) -{ - char *Dest, *Finder; - uint32_t Size; - - Finder = *Parser; - while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; - if (Finder >= End) - return EACC_FILECOMPATIBILITY; - if (Size = Finder - *Parser) { - if (nullptr == (Dest = *StoreTo = new char[Size + 1])) - return EACC_ALLOC; - for (; *Parser <= Finder; (*Parser)++, Dest++) - *Dest = **Parser; - } - else { - *StoreTo = nullptr; - (*Parser)++; - } - return 0; -} - -#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) -uint32_t ReadStringFromMemoryW(wchar_t **Parser, wchar_t *End, wchar_t **StoreTo, wchar_t *DebugString) -{ - //This is the debug version of ReadStringFromMemoryW function. This version shows MessageBox where - //read string is displayed - wchar_t *Dest, *Finder; - uint32_t Size; - wchar_t Debug[65536]; - - Finder = *Parser; - while ((*Finder != (wchar_t)0) && (Finder <= (wchar_t *)End)) Finder++; - mir_snwprintf(Debug, L"%s: %s,length is %d, remaining %d chars", DebugString, *Parser, Finder - *Parser, (wchar_t *)End - Finder); - MessageBoxW(NULL, Debug, L"debug", MB_OK); - if (Finder >= (wchar_t *)End) - return EACC_FILECOMPATIBILITY; - if (Size = Finder - *Parser) { - if (NULL == (Dest = *StoreTo = new wchar_t[Size + 1])) - return EACC_ALLOC; - for (; *Parser <= Finder; (*Parser)++, Dest++) - *Dest = **Parser; - } - else { - *StoreTo = NULL; - (*Parser)++; - } - return 0; -} -#endif //if defined(DEBUG...) - -uint32_t ReadStringFromMemoryW(wchar_t **Parser, wchar_t *End, wchar_t **StoreTo) -{ - wchar_t *Dest, *Finder; - uint32_t Size; - - Finder = *Parser; - while ((*Finder != (wchar_t)0) && (Finder <= (wchar_t *)End)) Finder++; - if (Finder >= (wchar_t *)End) - return EACC_FILECOMPATIBILITY; - if (Size = Finder - *Parser) { - if (nullptr == (Dest = *StoreTo = new wchar_t[Size + 1])) - return EACC_ALLOC; - for (; *Parser <= Finder; (*Parser)++, Dest++) - *Dest = **Parser; - } - else { - *StoreTo = nullptr; - (*Parser)++; - } - return 0; -} - -static uint32_t ReadNotificationFromMemory(char **Parser, char *End, YAMN_NOTIFICATION *Which) -{ - uint32_t Stat; - #ifdef DEBUG_FILEREAD - wchar_t Debug[65536]; - #endif - - Which->Flags = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"NFlags: %04x, remaining %d chars", Which->Flags, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - - Which->PopupB = *(COLORREF *)(*Parser); - (*Parser) += sizeof(COLORREF); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"PopupB: %04x, remaining %d chars", Which->PopupB, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - Which->PopupT = *(COLORREF *)(*Parser); - (*Parser) += sizeof(COLORREF); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"PopupT: %04x, remaining %d chars", Which->PopupT, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - Which->PopupTime = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"PopupTime: %04x, remaining %d chars", Which->PopupTime, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->App, L"App")) - #else - if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->App)) - #endif - return Stat; - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->AppParam, L"AppParam")) - #else - if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->AppParam)) - #endif - return Stat; - return 0; -} - -uint32_t ReadMessagesFromMemory(CAccount *Which, char **Parser, char *End) -{ - char *Finder; - uint32_t Size, Stat; - HYAMNMAIL ActualMail = nullptr; - struct CMimeItem *items; - char *ReadString; - - #ifdef DEBUG_FILEREAD - MessageBox(NULL, L"going to read messages, if any...", L"debug", MB_OK); - #endif - do { - Finder = *Parser; - while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; - if (Finder >= End) - return EACC_FILECOMPATIBILITY; - if (Size = Finder - *Parser) { - if (Which->Mails == nullptr) //First message in queue - { - if (nullptr == (Which->Mails = ActualMail = CreateAccountMail(Which))) - return EACC_ALLOC; - } - else { - if (nullptr == (ActualMail->Next = CreateAccountMail(Which))) { - return EACC_ALLOC; - } - ActualMail = ActualMail->Next; - } - items = nullptr; - #ifdef DEBUG_FILEREADMESSAGES - if (Stat = ReadStringFromMemory(Parser, End, &ActualMail->ID, L"ID")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &ActualMail->ID)) - #endif - return Stat; - // ActualMail->MailData=new MAILDATA; !!! mem leake !!! this is alloc by CreateAccountMail, no need for doubble alloc !!!! - - ActualMail->MailData->Size = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - ActualMail->Flags = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - ActualMail->Number = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - - if ((nullptr != Which->Plugin->MailFcn) && (nullptr != Which->Plugin->MailFcn->ReadMailOptsFcnPtr)) - Which->Plugin->MailFcn->ReadMailOptsFcnPtr(ActualMail, Parser, End); //read plugin mail settings from file - - do { - #if defined(DEBUG_FILEREADMESSAGES) || defined(DEBUG_FILEREAD) - if (Stat = ReadStringFromMemory(Parser, End, &ReadString, L"Name")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &ReadString)) - #endif - return Stat; - if (ReadString == nullptr) - break; - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%s", ReadString); - #endif - - if (items == nullptr) - items = ActualMail->MailData->TranslatedHeader = new struct CMimeItem; - else { - items->Next = new struct CMimeItem; - items = items->Next; - } - if (items == nullptr) - return EACC_ALLOC; - items->name = ReadString; - - #ifdef DEBUG_FILEREADMESSAGES - if (Stat = ReadStringFromMemory(Parser, End, &ReadString, L"Value")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &ReadString)) - #endif - return Stat; - items->value = ReadString; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%s\n", ReadString); - #endif - } while (1); - } - else - break; //no next messages, new account! - - } while (1); - (*Parser)++; - return 0; -} - -uint32_t ReadAccountFromMemory(CAccount *Which, char **Parser, char *End) -{ - uint32_t Stat; - #ifdef DEBUG_FILEREAD - wchar_t Debug[65536]; - #endif - //Read name of account - - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemory(Parser, End, &Which->Name, L"Name")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &Which->Name)) - #endif - return Stat; - if (Which->Name == nullptr) - return EACC_FILECOMPATIBILITY; - - //Read server parameters - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Name, L"Server")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Name)) - #endif - return Stat; - Which->Server->Port = *(uint16_t *)(*Parser); - (*Parser) += sizeof(uint16_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"Port: %d, remaining %d chars", Which->Server->Port, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Login, L"Login")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Login)) - #endif - return Stat; - #ifdef DEBUG_FILEREAD - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Passwd, L"Password")) - #else - if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Passwd)) - #endif - return Stat; - CodeDecodeString(Which->Server->Passwd, FALSE); - - //Read account flags - Which->Flags = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"Flags: %04x, remaining %d chars", Which->Flags, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - Which->StatusFlags = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"STFlags: %04x, remaining %d chars", Which->StatusFlags, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - Which->PluginFlags = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"PFlags: %04x, remaining %d chars", Which->PluginFlags, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - - //Read account miscellaneous parameters - Which->Interval = *(uint16_t *)(*Parser); - Which->TimeLeft = Which->Interval; //check on loading - (*Parser) += sizeof(uint16_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"Interval: %d, remaining %d chars", Which->Interval, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - - //Read notification parameters - if (Stat = ReadNotificationFromMemory(Parser, End, &Which->NewMailN)) - return Stat; - if (Stat = ReadNotificationFromMemory(Parser, End, &Which->NoNewMailN)) - return Stat; - if (Stat = ReadNotificationFromMemory(Parser, End, &Which->BadConnectN)) - return Stat; - - //Let plugin read its own data stored in file - if (Which->Plugin->Fcn != nullptr && Which->Plugin->Fcn->ReadPluginOptsFcnPtr != nullptr) - if (Stat = Which->Plugin->Fcn->ReadPluginOptsFcnPtr(Which, Parser, End)) - return Stat; - - // Read mails - WaitToWriteFcn(Which->MessagesAccessSO); - - if (Stat = ReadMessagesFromMemory(Which, Parser, End)) { - WriteDoneFcn(Which->MessagesAccessSO); - return Stat; - } - - WriteDoneFcn(Which->MessagesAccessSO); - - // Read timestamps - Which->LastChecked = *(SYSTEMTIME *)(*Parser); - (*Parser) += sizeof(SYSTEMTIME); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - - Which->LastSChecked = *(SYSTEMTIME *)(*Parser); - (*Parser) += sizeof(SYSTEMTIME); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - - Which->LastSynchronised = *(SYSTEMTIME *)(*Parser); - (*Parser) += sizeof(SYSTEMTIME); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - - Which->LastMail = *(SYSTEMTIME *)(*Parser); - (*Parser) += sizeof(SYSTEMTIME); - if (*Parser > End) //WARNING! There's only > at the end of testing - return EACC_FILECOMPATIBILITY; - - if (*Parser == End) - return EACC_ENDOFFILE; - return 0; -} - -static INT_PTR PerformAccountReading(HYAMNPROTOPLUGIN Plugin, char *MemFile, char *End) -{ - // Retrieve info for account from memory - char *Parser; - uint32_t Ver, Stat; - - CAccount *ActualAccount, *FirstAllocatedAccount; - - Ver = *(uint32_t *)MemFile; - if (Ver > YAMN_ACCOUNTFILEVERSION) { - delete[] MemFile; - return EACC_FILEVERSION; - } - Parser = MemFile + sizeof(Ver); - - SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); - - if (nullptr == (ActualAccount = (CAccount *)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)Plugin, (LPARAM)YAMN_ACCOUNTVERSION))) { - SWMRGDoneWriting(Plugin->AccountBrowserSO); - delete[] MemFile; - return EACC_ALLOC; - } - - FirstAllocatedAccount = ActualAccount; - - do { - CAccount *Temp; - - WaitToWriteFcn(ActualAccount->AccountAccessSO); - Stat = ReadAccountFromMemory(ActualAccount, &Parser, End); - - if (ActualAccount->StatusFlags & (YAMN_ACC_STARTA | YAMN_ACC_STARTS)) - ActualAccount->TimeLeft = 1; //check on loading - - if (Stat && (Stat != EACC_ENDOFFILE)) { - for (ActualAccount = FirstAllocatedAccount; ActualAccount != nullptr; ActualAccount = Temp) { - Temp = ActualAccount->Next; - delete ActualAccount; - } - delete[] MemFile; - if (Plugin->FirstAccount == FirstAllocatedAccount) - Plugin->FirstAccount = nullptr; - - SWMRGDoneWriting(Plugin->AccountBrowserSO); - return (INT_PTR)Stat; - } - - WriteDoneFcn(ActualAccount->AccountAccessSO); - - if ((Stat != EACC_ENDOFFILE) && (nullptr == (ActualAccount = (CAccount *)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)Plugin, (LPARAM)YAMN_ACCOUNTVERSION)))) { - for (ActualAccount = FirstAllocatedAccount; ActualAccount != nullptr; ActualAccount = Temp) { - Temp = ActualAccount->Next; - delete ActualAccount; - } - delete[] MemFile; - if (Plugin->FirstAccount == FirstAllocatedAccount) - Plugin->FirstAccount = nullptr; - - SWMRGDoneWriting(Plugin->AccountBrowserSO); - return EACC_ALLOC; - } - } while (Stat != EACC_ENDOFFILE); - - SWMRGDoneWriting(Plugin->AccountBrowserSO); - delete[] MemFile; - return 0; -} - -// Add accounts from file to memory -INT_PTR AddAccountsFromFileSvc(WPARAM wParam, LPARAM lParam) -{ - char *MemFile, *End; - uint32_t Stat = FileToMemory((wchar_t *)lParam, &MemFile, &End); - if (Stat != NO_ERROR) - return (INT_PTR)Stat; - - return PerformAccountReading((HYAMNPROTOPLUGIN)wParam, MemFile, End); -} - -uint32_t WriteStringToFile(HANDLE File, char *Source) -{ - DWORD Length, WrittenBytes; - char null = 0; - - if ((Source == nullptr) || !(Length = (uint32_t)mir_strlen(Source))) { - if (!WriteFile(File, &null, 1, &WrittenBytes, nullptr)) { - CloseHandle(File); - return EACC_SYSTEM; - } - } - else if (!WriteFile(File, Source, (Length + 1), &WrittenBytes, nullptr)) { - CloseHandle(File); - return EACC_SYSTEM; - } - return 0; -} - -uint32_t WriteStringToFileW(HANDLE File, wchar_t *Source) -{ - DWORD Length, WrittenBytes; - wchar_t null = (wchar_t)0; - - if ((Source == nullptr) || !(Length = (uint32_t)mir_wstrlen(Source))) { - if (!WriteFile(File, &null, sizeof(wchar_t), &WrittenBytes, nullptr)) { - CloseHandle(File); - return EACC_SYSTEM; - } - } - else if (!WriteFile(File, Source, (Length + 1) * sizeof(wchar_t), &WrittenBytes, nullptr)) - return EACC_SYSTEM; - return 0; -} - -DWORD WriteMessagesToFile(HANDLE File, CAccount *Which) -{ - DWORD WrittenBytes, Stat; - HYAMNMAIL ActualMail = (HYAMNMAIL)Which->Mails; - struct CMimeItem *items; - - while (ActualMail != nullptr) { - if (Stat = WriteStringToFile(File, ActualMail->ID)) - return Stat; - - if (!WriteFile(File, (char *)&ActualMail->MailData->Size, sizeof(ActualMail->MailData->Size), &WrittenBytes, nullptr) || - !WriteFile(File, (char *)&ActualMail->Flags, sizeof(ActualMail->Flags), &WrittenBytes, nullptr) || - !WriteFile(File, (char *)&ActualMail->Number, sizeof(ActualMail->Number), &WrittenBytes, nullptr)) - return EACC_SYSTEM; - if ((nullptr != Which->Plugin->MailFcn) && (nullptr != Which->Plugin->MailFcn->WriteMailOptsFcnPtr)) - Which->Plugin->MailFcn->WriteMailOptsFcnPtr(File, ActualMail); //write plugin mail options to file - for (items = ActualMail->MailData->TranslatedHeader; items != nullptr; items = items->Next) { - if (Stat = WriteStringToFile(File, items->name)) - return Stat; - if (Stat = WriteStringToFile(File, items->value)) - return Stat; - } - if (Stat = WriteStringToFile(File, "")) - return Stat; - ActualMail = ActualMail->Next; - } - if (Stat = WriteStringToFile(File, "")) - return Stat; - return 0; -} - -static INT_PTR PerformAccountWriting(HYAMNPROTOPLUGIN Plugin, HANDLE File) -{ - DWORD WrittenBytes, Stat; - CAccount *ActualAccount; - uint32_t Ver = YAMN_ACCOUNTFILEVERSION; - BOOL Writed = FALSE; - uint32_t ReturnValue = 0, EnterCode; - - SWMRGWaitToRead(Plugin->AccountBrowserSO, INFINITE); - - try { - for (ActualAccount = Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { - EnterCode = WaitToReadFcn(ActualAccount->AccountAccessSO); - if (EnterCode == WAIT_FINISH) //account is about to delete - { - ActualAccount = ActualAccount->Next; - continue; - } - if (EnterCode == WAIT_FAILED) //account is deleted - break; - - if ((ActualAccount->Name == nullptr) || (*ActualAccount->Name == (wchar_t)0)) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - continue; - } - - if (!Writed && !WriteFile(File, &Ver, sizeof(Ver), &WrittenBytes, nullptr)) - throw (uint32_t)EACC_SYSTEM; - - Writed = TRUE; - - if (Stat = WriteStringToFile(File, ActualAccount->Name)) - throw (uint32_t)Stat; - - if (Stat = WriteStringToFile(File, ActualAccount->Server->Name)) - throw (uint32_t)Stat; - - if (!WriteFile(File, (char *)&ActualAccount->Server->Port, 2, &WrittenBytes, nullptr)) - throw (uint32_t)EACC_SYSTEM; - - if ((Stat = WriteStringToFile(File, ActualAccount->Server->Login))) - throw (uint32_t)Stat; - - CodeDecodeString(ActualAccount->Server->Passwd, TRUE); - - if (Stat = WriteStringToFile(File, ActualAccount->Server->Passwd)) { - CodeDecodeString(ActualAccount->Server->Passwd, FALSE); - throw (uint32_t)Stat; - } - CodeDecodeString(ActualAccount->Server->Passwd, FALSE); - - if ((!WriteFile(File, (char *)&ActualAccount->Flags, sizeof(uint32_t), &WrittenBytes, nullptr) || - (!WriteFile(File, (char *)&ActualAccount->StatusFlags, sizeof(uint32_t), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->PluginFlags, sizeof(uint32_t), &WrittenBytes, nullptr)))) - throw (uint32_t)EACC_SYSTEM; - - if (!WriteFile(File, (char *)&ActualAccount->Interval, sizeof(uint16_t), &WrittenBytes, nullptr)) - throw (uint32_t)EACC_SYSTEM; - - if ((!WriteFile(File, (char *)&ActualAccount->NewMailN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) - throw (uint32_t)EACC_SYSTEM; - - if ((Stat = WriteStringToFileW(File, ActualAccount->NewMailN.App)) || - (Stat = WriteStringToFileW(File, ActualAccount->NewMailN.AppParam))) - throw (uint32_t)Stat; - - if ((!WriteFile(File, (char *)&ActualAccount->NoNewMailN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) - throw (uint32_t)EACC_SYSTEM; - - if ((Stat = WriteStringToFileW(File, ActualAccount->NoNewMailN.App)) || - (Stat = WriteStringToFileW(File, ActualAccount->NoNewMailN.AppParam))) - throw (uint32_t)Stat; - - if ((!WriteFile(File, (char *)&ActualAccount->BadConnectN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) - throw (uint32_t)EACC_SYSTEM; - - if ((Stat = WriteStringToFileW(File, ActualAccount->BadConnectN.App)) || - (Stat = WriteStringToFileW(File, ActualAccount->BadConnectN.AppParam))) - throw (uint32_t)Stat; - - //Let plugin write its own values into file - if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->WritePluginOptsFcnPtr != nullptr) - if (Stat = ActualAccount->Plugin->Fcn->WritePluginOptsFcnPtr(File, ActualAccount)) - throw (uint32_t)Stat; - - WaitToReadFcn(ActualAccount->MessagesAccessSO); - - if (Stat = WriteMessagesToFile(File, ActualAccount)) { - - ReadDoneFcn(ActualAccount->MessagesAccessSO); - throw (uint32_t)Stat; - } - - ReadDoneFcn(ActualAccount->MessagesAccessSO); - - if ((!WriteFile(File, (char *)&ActualAccount->LastChecked, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->LastSChecked, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->LastSynchronised, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&ActualAccount->LastMail, sizeof(SYSTEMTIME), &WrittenBytes, nullptr))) - throw (uint32_t)Stat; - - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - } - catch (uint32_t ErrorCode) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - ReturnValue = ErrorCode; - } - - SWMRGDoneReading(Plugin->AccountBrowserSO); - CloseHandle(File); - return 0; -} - -// Writes accounts to file -INT_PTR WriteAccountsToFileSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - - mir_cslock lck(csFileWritingCS); - HANDLE hFile = CreateFile((wchar_t *)lParam, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) - return EACC_SYSTEM; - - return PerformAccountWriting(Plugin, hFile); -} - -INT_PTR FindAccountByNameSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - char *SearchedAccount = (char *)lParam; - CAccount *Finder; - - SWMRGWaitToRead(Plugin->AccountBrowserSO, INFINITE); - - for (Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) - if ((Finder->Name != nullptr) && (0 == mir_strcmp(SearchedAccount, Finder->Name))) - break; - - SWMRGDoneReading(Plugin->AccountBrowserSO); - return (INT_PTR)Finder; -} - -INT_PTR GetNextFreeAccountSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - CAccount *Finder; - - if (Plugin->FirstAccount == nullptr) { - Plugin->FirstAccount = (CAccount *)CallService(MS_YAMN_CREATEPLUGINACCOUNT, wParam, lParam); - return (INT_PTR)Plugin->FirstAccount; - } - for (Finder = Plugin->FirstAccount; Finder->Next != nullptr; Finder = Finder->Next); - Finder->Next = (CAccount *)CallService(MS_YAMN_CREATEPLUGINACCOUNT, wParam, lParam); - return (INT_PTR)Finder->Next; -} - -INT_PTR DeleteAccountSvc(WPARAM wParam, LPARAM lParam) -{ - //Deleting account works on these steps: - //1. set signal that account should stop activity (set event) - // setting this event we achieve, that any access to account is failed, - // so threads do not start any work with accounts (better saying threads of plugins should not start) - //2. wait to get write access to chained list of accounts - //3. we can write to chained list, so we change chain not to show to actual account - // now, any thread browsing list of accounts does not browse through actual account - // actual account seems to be hidden (it exists, but it is not in accounts chained list (chained list=queue)) - //Now, we should delete account from memory, BUT!!! - // Any thread can still be waked up and start asking account synchronizing object - // If account is deleted, asking about access to read account can throw memory exception (reading for - // a synchronizing object from memory, that was deleted) - //So, we cannot now delete account. We have to wait until we are sure no thread will be using account anymore - // (or to the end of Miranda, but problem is in allocated memory- it is allocated and Miranda is SMALLER, faster, easier, isn't it?) - // This deleting is achieved in 2 ways: - // We have event in UsingThreads synchronization objects. This event signals that no thread will use actual account - // 1. Any thread using account first increment UsingThread, so we know that account is used - // 2. If thread is about to close, it should decrement UsingThread - // 3. If thread creates another thread, that will use account, caller has to wait until the new thread does not - // increment UsingThreads (imagine that caller ends before the new thread set it: if no other thread is using - // account, account is automaticaly (decreasing UsingThreads) signaled as "not used" and we delete it. But then - // new thread is going to read account...). - //4. wait until UsingThread Event is signaled - //5. delete account from memory - - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - CAccount *Which = (CAccount *)lParam; - CAccount *Finder; - - //1. set stop signal - StopSignalFcn(Which); - WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_STOPACCOUNT, (WPARAM)Which, 0); - if (Plugin->Fcn->StopAccountFcnPtr != nullptr) - Plugin->Fcn->StopAccountFcnPtr(Which); - - //2. wait to get write access - SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); - - //3. remove from queue (chained list) - if (Plugin->FirstAccount == nullptr) { - SWMRGDoneWriting(Plugin->AccountBrowserSO); - return 0; - } - if (Plugin->FirstAccount == Which) { - Finder = Plugin->FirstAccount->Next; - Plugin->FirstAccount = Finder; - } - else { - for (Finder = Plugin->FirstAccount; Which != Finder->Next; Finder = Finder->Next); - Finder->Next = Finder->Next->Next; - } - - //leave write access - SWMRGDoneWriting(Plugin->AccountBrowserSO); - - //4. wait while event "UsingThread" is not signaled - // And what to do, if this event will be signaled in 1 hour? (Although it's paranoia, because we have sent "delete signal", so - // other threads do not start any new work with actual account) We will wait in blocked state? - // No, of course not. We will create new thread, that will wait and additionally remove our thread in background. - //5. So, the last point (deleting from memory) is performed in new DeleteAccountInBackground thread - - if ((Plugin->Fcn != nullptr) && (Plugin->Fcn->WriteAccountsFcnPtr != nullptr)) - Plugin->Fcn->WriteAccountsFcnPtr(); - CloseHandle(mir_forkthread(DeleteAccountInBackground, (void *)Which)); - - //Now, plugin can consider account as deleted, but plugin really can achieve deleting this account from memory when using - //event UsingThreads. - return 1; -} - -void __cdecl DeleteAccountInBackground(void *Value) -{ - CAccount *Which = (CAccount *)Value; - WaitForSingleObject(Which->UsingThreads->Event, INFINITE); - CallService(MS_YAMN_DELETEPLUGINACCOUNT, (WPARAM)Which, 0); -} - -int StopAccounts(HYAMNPROTOPLUGIN Plugin) -{ - CAccount *Finder; - - //1. wait to get write access - SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); - - for (Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { - //2. set stop signal - StopSignalFcn(Finder); - WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_STOPACCOUNT, (WPARAM)Finder, 0); - if (Plugin->Fcn->StopAccountFcnPtr != nullptr) - Plugin->Fcn->StopAccountFcnPtr(Finder); - } - - //leave write access - SWMRGDoneWriting(Plugin->AccountBrowserSO); - - //Now, account is stopped. It can be removed from memory... - return 1; -} - -int WaitForAllAccounts(HYAMNPROTOPLUGIN Plugin, BOOL GetAccountBrowserAccess) -{ - if (GetAccountBrowserAccess) { - //1. wait to get write access - SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); - } - for (CAccount *Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { - //2. wait for signal that account is not in use - WaitForSingleObject(Finder->UsingThreads->Event, INFINITE); - SetEvent(Finder->UsingThreads->Event); - } - if (GetAccountBrowserAccess) { - //leave write access - SWMRGDoneWriting(Plugin->AccountBrowserSO); - } - - return 1; -} - -int DeleteAccounts(HYAMNPROTOPLUGIN Plugin) -{ - //1. wait to get write access - SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); - - WaitForAllAccounts(Plugin, FALSE); - - for (CAccount *Finder = Plugin->FirstAccount; Finder != nullptr;) { - CAccount *Next = Finder->Next; - DeletePluginAccountSvc((WPARAM)Finder, 0); - Finder = Next; - } - - //leave write access - SWMRGDoneWriting(Plugin->AccountBrowserSO); - return 1; -} - -void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value) -{ - if (Which == nullptr) - return; - - mir_cslock lck(csAccountStatusCS); - mir_wstrcpy(Value, Which->Status); -} - -void WINAPI SetStatusFcn(CAccount *Which, wchar_t *Value) -{ - if (Which != nullptr) { - mir_cslock lck(csAccountStatusCS); - mir_wstrcpy(Which->Status, Value); - } - - WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGESTATUS, (WPARAM)Which, 0); -} +/* + * This code implements manipulation with accounts + * such as reading accounts from file, writing them to file, + * finding account by name etc. + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + + // Account status CS + // When we check some account, thread should change status of account to idle, connecting etc. + // So if we want to read status, we have to successfully write and then read. +static mir_cs csAccountStatusCS; + +// File Writing CS +// When 2 threads want to write to file... +static mir_cs csFileWritingCS; + +struct CExportedFunctions AccountExportedFcn[] = +{ + {YAMN_GETSTATUSID, (void *)GetStatusFcn}, + {YAMN_SETSTATUSID, (void *)SetStatusFcn}, +}; + +struct CExportedServices AccountExportedSvc[] = +{ + {MS_YAMN_CREATEPLUGINACCOUNT, CreatePluginAccountSvc}, + {MS_YAMN_DELETEPLUGINACCOUNT, DeletePluginAccountSvc}, + {MS_YAMN_FINDACCOUNTBYNAME, FindAccountByNameSvc}, + {MS_YAMN_GETNEXTFREEACCOUNT, GetNextFreeAccountSvc}, + {MS_YAMN_DELETEACCOUNT, DeletePluginAccountSvc}, + {MS_YAMN_READACCOUNTS, AddAccountsFromFileSvc}, + {MS_YAMN_WRITEACCOUNTS, WriteAccountsToFileSvc}, +}; + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +INT_PTR CreatePluginAccountSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + uint32_t AccountVersion = (uint32_t)lParam; + + //test if we are going to initialize members of suitable structure (structures of plugin and YAMN must match) + if (AccountVersion != YAMN_ACCOUNTVERSION) + return NULL; + + if (Plugin != nullptr) { + CAccount *NewAccount; + if (Plugin->Fcn->NewAccountFcnPtr != nullptr) + //Let plugin create its own structure, which can be derived from CAccount structure + NewAccount = Plugin->Fcn->NewAccountFcnPtr(Plugin, YAMN_ACCOUNTVERSION); + else + //We suggest plugin uses standard CAccount structure, so we create it + NewAccount = new struct CAccount; + + //If not created successfully + if (NewAccount == nullptr) + return NULL; + + NewAccount->Plugin = Plugin; + //Init every members of structure, used by YAMN + InitAccount(NewAccount); + + return (INT_PTR)NewAccount; + } + return NULL; +} + +INT_PTR DeletePluginAccountSvc(WPARAM wParam, LPARAM) +{ + CAccount *OldAccount = (CAccount *)wParam; + + if (OldAccount->Plugin->Fcn != nullptr) { + // Deinit every members and allocated fields of structure used by YAMN + DeInitAccount(OldAccount); + if (OldAccount->Plugin->Fcn->DeleteAccountFcnPtr != nullptr) { + // Let plugin delete its own CAccount derived structure + OldAccount->Plugin->Fcn->DeleteAccountFcnPtr(OldAccount); + } + else { + delete OldAccount; //consider account as standard YAMN CAccount *and use its own destructor + } + return 1; + } + delete OldAccount; //consider account as standard YAMN CAccount *, not initialized before and use its own destructor + return 1; +} + +int InitAccount(CAccount *Which) +{ + //initialize synchronizing objects + Which->AccountAccessSO = new SWMRG; + SWMRGInitialize(Which->AccountAccessSO, nullptr); + Which->MessagesAccessSO = new SWMRG; + SWMRGInitialize(Which->MessagesAccessSO, nullptr); + Which->UsingThreads = new SCOUNTER; + SWMRGInitialize(Which->MessagesAccessSO, nullptr); + + //zero memory, where timestamps are stored + memset(&Which->LastChecked, 0, sizeof(Which->LastChecked)); + memset(&Which->LastSChecked, 0, sizeof(Which->LastSChecked)); + memset(&Which->LastSynchronised, 0, sizeof(Which->LastSynchronised)); + memset(&Which->LastMail, 0, sizeof(Which->LastMail)); + + Which->Name = nullptr; + Which->Mails = nullptr; + Which->Interval = 0; + Which->Flags = 0; + Which->StatusFlags = YAMN_ACC_ST1 + YAMN_ACC_ST7; + Which->Next = nullptr; + + Which->Server = new struct CServer; + Which->AbleToWork = TRUE; + + return 1; +} + +void DeInitAccount(CAccount *Which) +{ + //delete YAMN allocated fields + if (Which->Name != nullptr) + delete[] Which->Name; + if (Which->Server != nullptr) { + if (Which->Server->Name != nullptr) + delete[] Which->Server->Name; + if (Which->Server->Login != nullptr) + delete[] Which->Server->Login; + if (Which->Server->Passwd != nullptr) + delete[] Which->Server->Passwd; + delete[] Which->Server; + } + + SWMRGDelete(Which->AccountAccessSO); + delete Which->AccountAccessSO; + SWMRGDelete(Which->MessagesAccessSO); + delete Which->MessagesAccessSO; + delete Which->UsingThreads; + DeleteMessagesToEndFcn(Which, (HYAMNMAIL)Which->Mails); +} + +void StopSignalFcn(CAccount *Which) +//set event that we are going to delete account +{ + Which->AbleToWork = FALSE; + //do not use synchronizing objects anymore + //any access to these objects then ends with WAIT_FAILED + SetEvent(Which->AccountAccessSO->hFinishEV); + SetEvent(Which->MessagesAccessSO->hFinishEV); +} + +void CodeDecodeString(char *Dest, BOOL Encrypt) +{ + wchar_t Code = STARTCODEPSW; + + if (Dest == nullptr) + return; + + for (; *Dest != (wchar_t)0; Dest++) { + if (Encrypt) + *Dest = *Dest + Code; + else + *Dest = *Dest - Code; + Code += (wchar_t)ADDCODEPSW; + } +} + +static uint32_t PostFileToMemory(HANDLE File, char **MemFile, char **End) +{ + DWORD FileSize, ReadBytes; + if (!(FileSize = GetFileSize(File, nullptr))) { + CloseHandle(File); + return EACC_FILESIZE; + } + + //allocate space in memory, where we copy the whole file + if (nullptr == (*MemFile = new char[FileSize])) { + CloseHandle(File); + return EACC_ALLOC; + } + + //copy file to memory + if (!ReadFile(File, (LPVOID)*MemFile, FileSize, &ReadBytes, nullptr)) { + CloseHandle(File); + delete[] * MemFile; + return EACC_SYSTEM; + } + CloseHandle(File); + *End = *MemFile + FileSize; + return 0; +} + +uint32_t FileToMemory(wchar_t *FileName, char **MemFile, char **End) +{ + HANDLE hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + return EACC_SYSTEM; + + return PostFileToMemory(hFile, MemFile, End); +} + +#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) +uint32_t ReadStringFromMemory(char **Parser, wchar_t *End, char **StoreTo, wchar_t *DebugString) +{ + //This is the debug version of ReadStringFromMemory function. This version shows MessageBox where + //read string is displayed + wchar_t *Dest, *Finder; + uint32_t Size; + wchar_t Debug[65536]; + + Finder = *Parser; + while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; + mir_snwprintf(Debug, L"%s: %s,length is %d, remaining %d chars", DebugString, *Parser, Finder - *Parser, End - Finder); + MessageBox(NULL, Debug, L"debug", MB_OK); + if (Finder >= End) + return EACC_FILECOMPATIBILITY; + if (Size = Finder - *Parser) { + if (NULL == (Dest = *StoreTo = new wchar_t[Size + 1])) + return EACC_ALLOC; + for (; *Parser <= Finder; (*Parser)++, Dest++) + *Dest = **Parser; + } + else { + *StoreTo = NULL; + (*Parser)++; + } + return 0; +} +#endif + +uint32_t ReadStringFromMemory(char **Parser, char *End, char **StoreTo) +{ + char *Dest, *Finder; + uint32_t Size; + + Finder = *Parser; + while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; + if (Finder >= End) + return EACC_FILECOMPATIBILITY; + if (Size = Finder - *Parser) { + if (nullptr == (Dest = *StoreTo = new char[Size + 1])) + return EACC_ALLOC; + for (; *Parser <= Finder; (*Parser)++, Dest++) + *Dest = **Parser; + } + else { + *StoreTo = nullptr; + (*Parser)++; + } + return 0; +} + +#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) +uint32_t ReadStringFromMemoryW(wchar_t **Parser, wchar_t *End, wchar_t **StoreTo, wchar_t *DebugString) +{ + //This is the debug version of ReadStringFromMemoryW function. This version shows MessageBox where + //read string is displayed + wchar_t *Dest, *Finder; + uint32_t Size; + wchar_t Debug[65536]; + + Finder = *Parser; + while ((*Finder != (wchar_t)0) && (Finder <= (wchar_t *)End)) Finder++; + mir_snwprintf(Debug, L"%s: %s,length is %d, remaining %d chars", DebugString, *Parser, Finder - *Parser, (wchar_t *)End - Finder); + MessageBoxW(NULL, Debug, L"debug", MB_OK); + if (Finder >= (wchar_t *)End) + return EACC_FILECOMPATIBILITY; + if (Size = Finder - *Parser) { + if (NULL == (Dest = *StoreTo = new wchar_t[Size + 1])) + return EACC_ALLOC; + for (; *Parser <= Finder; (*Parser)++, Dest++) + *Dest = **Parser; + } + else { + *StoreTo = NULL; + (*Parser)++; + } + return 0; +} +#endif //if defined(DEBUG...) + +uint32_t ReadStringFromMemoryW(wchar_t **Parser, wchar_t *End, wchar_t **StoreTo) +{ + wchar_t *Dest, *Finder; + uint32_t Size; + + Finder = *Parser; + while ((*Finder != (wchar_t)0) && (Finder <= (wchar_t *)End)) Finder++; + if (Finder >= (wchar_t *)End) + return EACC_FILECOMPATIBILITY; + if (Size = Finder - *Parser) { + if (nullptr == (Dest = *StoreTo = new wchar_t[Size + 1])) + return EACC_ALLOC; + for (; *Parser <= Finder; (*Parser)++, Dest++) + *Dest = **Parser; + } + else { + *StoreTo = nullptr; + (*Parser)++; + } + return 0; +} + +static uint32_t ReadNotificationFromMemory(char **Parser, char *End, YAMN_NOTIFICATION *Which) +{ + uint32_t Stat; + #ifdef DEBUG_FILEREAD + wchar_t Debug[65536]; + #endif + + Which->Flags = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"NFlags: %04x, remaining %d chars", Which->Flags, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + + Which->PopupB = *(COLORREF *)(*Parser); + (*Parser) += sizeof(COLORREF); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"PopupB: %04x, remaining %d chars", Which->PopupB, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + Which->PopupT = *(COLORREF *)(*Parser); + (*Parser) += sizeof(COLORREF); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"PopupT: %04x, remaining %d chars", Which->PopupT, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + Which->PopupTime = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"PopupTime: %04x, remaining %d chars", Which->PopupTime, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->App, L"App")) + #else + if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->App)) + #endif + return Stat; + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->AppParam, L"AppParam")) + #else + if (Stat = ReadStringFromMemoryW((wchar_t **)Parser, (wchar_t *)End, &Which->AppParam)) + #endif + return Stat; + return 0; +} + +uint32_t ReadMessagesFromMemory(CAccount *Which, char **Parser, char *End) +{ + char *Finder; + uint32_t Size, Stat; + HYAMNMAIL ActualMail = nullptr; + struct CMimeItem *items; + char *ReadString; + + #ifdef DEBUG_FILEREAD + MessageBox(NULL, L"going to read messages, if any...", L"debug", MB_OK); + #endif + do { + Finder = *Parser; + while ((*Finder != (wchar_t)0) && (Finder <= End)) Finder++; + if (Finder >= End) + return EACC_FILECOMPATIBILITY; + if (Size = Finder - *Parser) { + if (Which->Mails == nullptr) //First message in queue + { + if (nullptr == (Which->Mails = ActualMail = CreateAccountMail(Which))) + return EACC_ALLOC; + } + else { + if (nullptr == (ActualMail->Next = CreateAccountMail(Which))) { + return EACC_ALLOC; + } + ActualMail = ActualMail->Next; + } + items = nullptr; + #ifdef DEBUG_FILEREADMESSAGES + if (Stat = ReadStringFromMemory(Parser, End, &ActualMail->ID, L"ID")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &ActualMail->ID)) + #endif + return Stat; + // ActualMail->MailData=new MAILDATA; !!! mem leake !!! this is alloc by CreateAccountMail, no need for doubble alloc !!!! + + ActualMail->MailData->Size = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + ActualMail->Flags = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + ActualMail->Number = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + + if ((nullptr != Which->Plugin->MailFcn) && (nullptr != Which->Plugin->MailFcn->ReadMailOptsFcnPtr)) + Which->Plugin->MailFcn->ReadMailOptsFcnPtr(ActualMail, Parser, End); //read plugin mail settings from file + + do { + #if defined(DEBUG_FILEREADMESSAGES) || defined(DEBUG_FILEREAD) + if (Stat = ReadStringFromMemory(Parser, End, &ReadString, L"Name")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &ReadString)) + #endif + return Stat; + if (ReadString == nullptr) + break; + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%s", ReadString); + #endif + + if (items == nullptr) + items = ActualMail->MailData->TranslatedHeader = new struct CMimeItem; + else { + items->Next = new struct CMimeItem; + items = items->Next; + } + if (items == nullptr) + return EACC_ALLOC; + items->name = ReadString; + + #ifdef DEBUG_FILEREADMESSAGES + if (Stat = ReadStringFromMemory(Parser, End, &ReadString, L"Value")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &ReadString)) + #endif + return Stat; + items->value = ReadString; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%s\n", ReadString); + #endif + } while (1); + } + else + break; //no next messages, new account! + + } while (1); + (*Parser)++; + return 0; +} + +uint32_t ReadAccountFromMemory(CAccount *Which, char **Parser, char *End) +{ + uint32_t Stat; + #ifdef DEBUG_FILEREAD + wchar_t Debug[65536]; + #endif + //Read name of account + + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemory(Parser, End, &Which->Name, L"Name")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &Which->Name)) + #endif + return Stat; + if (Which->Name == nullptr) + return EACC_FILECOMPATIBILITY; + + //Read server parameters + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Name, L"Server")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Name)) + #endif + return Stat; + Which->Server->Port = *(uint16_t *)(*Parser); + (*Parser) += sizeof(uint16_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"Port: %d, remaining %d chars", Which->Server->Port, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Login, L"Login")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Login)) + #endif + return Stat; + #ifdef DEBUG_FILEREAD + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Passwd, L"Password")) + #else + if (Stat = ReadStringFromMemory(Parser, End, &Which->Server->Passwd)) + #endif + return Stat; + CodeDecodeString(Which->Server->Passwd, FALSE); + + //Read account flags + Which->Flags = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"Flags: %04x, remaining %d chars", Which->Flags, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + Which->StatusFlags = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"STFlags: %04x, remaining %d chars", Which->StatusFlags, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + Which->PluginFlags = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"PFlags: %04x, remaining %d chars", Which->PluginFlags, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + + //Read account miscellaneous parameters + Which->Interval = *(uint16_t *)(*Parser); + Which->TimeLeft = Which->Interval; //check on loading + (*Parser) += sizeof(uint16_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"Interval: %d, remaining %d chars", Which->Interval, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + + //Read notification parameters + if (Stat = ReadNotificationFromMemory(Parser, End, &Which->NewMailN)) + return Stat; + if (Stat = ReadNotificationFromMemory(Parser, End, &Which->NoNewMailN)) + return Stat; + if (Stat = ReadNotificationFromMemory(Parser, End, &Which->BadConnectN)) + return Stat; + + //Let plugin read its own data stored in file + if (Which->Plugin->Fcn != nullptr && Which->Plugin->Fcn->ReadPluginOptsFcnPtr != nullptr) + if (Stat = Which->Plugin->Fcn->ReadPluginOptsFcnPtr(Which, Parser, End)) + return Stat; + + // Read mails + WaitToWriteFcn(Which->MessagesAccessSO); + + if (Stat = ReadMessagesFromMemory(Which, Parser, End)) { + WriteDoneFcn(Which->MessagesAccessSO); + return Stat; + } + + WriteDoneFcn(Which->MessagesAccessSO); + + // Read timestamps + Which->LastChecked = *(SYSTEMTIME *)(*Parser); + (*Parser) += sizeof(SYSTEMTIME); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + + Which->LastSChecked = *(SYSTEMTIME *)(*Parser); + (*Parser) += sizeof(SYSTEMTIME); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + + Which->LastSynchronised = *(SYSTEMTIME *)(*Parser); + (*Parser) += sizeof(SYSTEMTIME); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + + Which->LastMail = *(SYSTEMTIME *)(*Parser); + (*Parser) += sizeof(SYSTEMTIME); + if (*Parser > End) //WARNING! There's only > at the end of testing + return EACC_FILECOMPATIBILITY; + + if (*Parser == End) + return EACC_ENDOFFILE; + return 0; +} + +static INT_PTR PerformAccountReading(HYAMNPROTOPLUGIN Plugin, char *MemFile, char *End) +{ + // Retrieve info for account from memory + char *Parser; + uint32_t Ver, Stat; + + CAccount *ActualAccount, *FirstAllocatedAccount; + + Ver = *(uint32_t *)MemFile; + if (Ver > YAMN_ACCOUNTFILEVERSION) { + delete[] MemFile; + return EACC_FILEVERSION; + } + Parser = MemFile + sizeof(Ver); + + SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); + + if (nullptr == (ActualAccount = (CAccount *)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)Plugin, (LPARAM)YAMN_ACCOUNTVERSION))) { + SWMRGDoneWriting(Plugin->AccountBrowserSO); + delete[] MemFile; + return EACC_ALLOC; + } + + FirstAllocatedAccount = ActualAccount; + + do { + CAccount *Temp; + + WaitToWriteFcn(ActualAccount->AccountAccessSO); + Stat = ReadAccountFromMemory(ActualAccount, &Parser, End); + + if (ActualAccount->StatusFlags & (YAMN_ACC_STARTA | YAMN_ACC_STARTS)) + ActualAccount->TimeLeft = 1; //check on loading + + if (Stat && (Stat != EACC_ENDOFFILE)) { + for (ActualAccount = FirstAllocatedAccount; ActualAccount != nullptr; ActualAccount = Temp) { + Temp = ActualAccount->Next; + delete ActualAccount; + } + delete[] MemFile; + if (Plugin->FirstAccount == FirstAllocatedAccount) + Plugin->FirstAccount = nullptr; + + SWMRGDoneWriting(Plugin->AccountBrowserSO); + return (INT_PTR)Stat; + } + + WriteDoneFcn(ActualAccount->AccountAccessSO); + + if ((Stat != EACC_ENDOFFILE) && (nullptr == (ActualAccount = (CAccount *)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)Plugin, (LPARAM)YAMN_ACCOUNTVERSION)))) { + for (ActualAccount = FirstAllocatedAccount; ActualAccount != nullptr; ActualAccount = Temp) { + Temp = ActualAccount->Next; + delete ActualAccount; + } + delete[] MemFile; + if (Plugin->FirstAccount == FirstAllocatedAccount) + Plugin->FirstAccount = nullptr; + + SWMRGDoneWriting(Plugin->AccountBrowserSO); + return EACC_ALLOC; + } + } while (Stat != EACC_ENDOFFILE); + + SWMRGDoneWriting(Plugin->AccountBrowserSO); + delete[] MemFile; + return 0; +} + +// Add accounts from file to memory +INT_PTR AddAccountsFromFileSvc(WPARAM wParam, LPARAM lParam) +{ + char *MemFile, *End; + uint32_t Stat = FileToMemory((wchar_t *)lParam, &MemFile, &End); + if (Stat != NO_ERROR) + return (INT_PTR)Stat; + + return PerformAccountReading((HYAMNPROTOPLUGIN)wParam, MemFile, End); +} + +uint32_t WriteStringToFile(HANDLE File, char *Source) +{ + DWORD Length, WrittenBytes; + char null = 0; + + if ((Source == nullptr) || !(Length = (uint32_t)mir_strlen(Source))) { + if (!WriteFile(File, &null, 1, &WrittenBytes, nullptr)) { + CloseHandle(File); + return EACC_SYSTEM; + } + } + else if (!WriteFile(File, Source, (Length + 1), &WrittenBytes, nullptr)) { + CloseHandle(File); + return EACC_SYSTEM; + } + return 0; +} + +uint32_t WriteStringToFileW(HANDLE File, wchar_t *Source) +{ + DWORD Length, WrittenBytes; + wchar_t null = (wchar_t)0; + + if ((Source == nullptr) || !(Length = (uint32_t)mir_wstrlen(Source))) { + if (!WriteFile(File, &null, sizeof(wchar_t), &WrittenBytes, nullptr)) { + CloseHandle(File); + return EACC_SYSTEM; + } + } + else if (!WriteFile(File, Source, (Length + 1) * sizeof(wchar_t), &WrittenBytes, nullptr)) + return EACC_SYSTEM; + return 0; +} + +DWORD WriteMessagesToFile(HANDLE File, CAccount *Which) +{ + DWORD WrittenBytes, Stat; + HYAMNMAIL ActualMail = (HYAMNMAIL)Which->Mails; + struct CMimeItem *items; + + while (ActualMail != nullptr) { + if (Stat = WriteStringToFile(File, ActualMail->ID)) + return Stat; + + if (!WriteFile(File, (char *)&ActualMail->MailData->Size, sizeof(ActualMail->MailData->Size), &WrittenBytes, nullptr) || + !WriteFile(File, (char *)&ActualMail->Flags, sizeof(ActualMail->Flags), &WrittenBytes, nullptr) || + !WriteFile(File, (char *)&ActualMail->Number, sizeof(ActualMail->Number), &WrittenBytes, nullptr)) + return EACC_SYSTEM; + if ((nullptr != Which->Plugin->MailFcn) && (nullptr != Which->Plugin->MailFcn->WriteMailOptsFcnPtr)) + Which->Plugin->MailFcn->WriteMailOptsFcnPtr(File, ActualMail); //write plugin mail options to file + for (items = ActualMail->MailData->TranslatedHeader; items != nullptr; items = items->Next) { + if (Stat = WriteStringToFile(File, items->name)) + return Stat; + if (Stat = WriteStringToFile(File, items->value)) + return Stat; + } + if (Stat = WriteStringToFile(File, "")) + return Stat; + ActualMail = ActualMail->Next; + } + if (Stat = WriteStringToFile(File, "")) + return Stat; + return 0; +} + +static INT_PTR PerformAccountWriting(HYAMNPROTOPLUGIN Plugin, HANDLE File) +{ + DWORD WrittenBytes, Stat; + CAccount *ActualAccount; + uint32_t Ver = YAMN_ACCOUNTFILEVERSION; + BOOL Writed = FALSE; + uint32_t ReturnValue = 0, EnterCode; + + SWMRGWaitToRead(Plugin->AccountBrowserSO, INFINITE); + + try { + for (ActualAccount = Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { + EnterCode = WaitToReadFcn(ActualAccount->AccountAccessSO); + if (EnterCode == WAIT_FINISH) //account is about to delete + { + ActualAccount = ActualAccount->Next; + continue; + } + if (EnterCode == WAIT_FAILED) //account is deleted + break; + + if ((ActualAccount->Name == nullptr) || (*ActualAccount->Name == (wchar_t)0)) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + continue; + } + + if (!Writed && !WriteFile(File, &Ver, sizeof(Ver), &WrittenBytes, nullptr)) + throw (uint32_t)EACC_SYSTEM; + + Writed = TRUE; + + if (Stat = WriteStringToFile(File, ActualAccount->Name)) + throw (uint32_t)Stat; + + if (Stat = WriteStringToFile(File, ActualAccount->Server->Name)) + throw (uint32_t)Stat; + + if (!WriteFile(File, (char *)&ActualAccount->Server->Port, 2, &WrittenBytes, nullptr)) + throw (uint32_t)EACC_SYSTEM; + + if ((Stat = WriteStringToFile(File, ActualAccount->Server->Login))) + throw (uint32_t)Stat; + + CodeDecodeString(ActualAccount->Server->Passwd, TRUE); + + if (Stat = WriteStringToFile(File, ActualAccount->Server->Passwd)) { + CodeDecodeString(ActualAccount->Server->Passwd, FALSE); + throw (uint32_t)Stat; + } + CodeDecodeString(ActualAccount->Server->Passwd, FALSE); + + if ((!WriteFile(File, (char *)&ActualAccount->Flags, sizeof(uint32_t), &WrittenBytes, nullptr) || + (!WriteFile(File, (char *)&ActualAccount->StatusFlags, sizeof(uint32_t), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->PluginFlags, sizeof(uint32_t), &WrittenBytes, nullptr)))) + throw (uint32_t)EACC_SYSTEM; + + if (!WriteFile(File, (char *)&ActualAccount->Interval, sizeof(uint16_t), &WrittenBytes, nullptr)) + throw (uint32_t)EACC_SYSTEM; + + if ((!WriteFile(File, (char *)&ActualAccount->NewMailN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NewMailN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) + throw (uint32_t)EACC_SYSTEM; + + if ((Stat = WriteStringToFileW(File, ActualAccount->NewMailN.App)) || + (Stat = WriteStringToFileW(File, ActualAccount->NewMailN.AppParam))) + throw (uint32_t)Stat; + + if ((!WriteFile(File, (char *)&ActualAccount->NoNewMailN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->NoNewMailN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) + throw (uint32_t)EACC_SYSTEM; + + if ((Stat = WriteStringToFileW(File, ActualAccount->NoNewMailN.App)) || + (Stat = WriteStringToFileW(File, ActualAccount->NoNewMailN.AppParam))) + throw (uint32_t)Stat; + + if ((!WriteFile(File, (char *)&ActualAccount->BadConnectN.Flags, sizeof(uint32_t), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupB, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupT, sizeof(COLORREF), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->BadConnectN.PopupTime, sizeof(uint32_t), &WrittenBytes, nullptr))) + throw (uint32_t)EACC_SYSTEM; + + if ((Stat = WriteStringToFileW(File, ActualAccount->BadConnectN.App)) || + (Stat = WriteStringToFileW(File, ActualAccount->BadConnectN.AppParam))) + throw (uint32_t)Stat; + + //Let plugin write its own values into file + if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->WritePluginOptsFcnPtr != nullptr) + if (Stat = ActualAccount->Plugin->Fcn->WritePluginOptsFcnPtr(File, ActualAccount)) + throw (uint32_t)Stat; + + WaitToReadFcn(ActualAccount->MessagesAccessSO); + + if (Stat = WriteMessagesToFile(File, ActualAccount)) { + + ReadDoneFcn(ActualAccount->MessagesAccessSO); + throw (uint32_t)Stat; + } + + ReadDoneFcn(ActualAccount->MessagesAccessSO); + + if ((!WriteFile(File, (char *)&ActualAccount->LastChecked, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->LastSChecked, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->LastSynchronised, sizeof(SYSTEMTIME), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&ActualAccount->LastMail, sizeof(SYSTEMTIME), &WrittenBytes, nullptr))) + throw (uint32_t)Stat; + + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + } + catch (uint32_t ErrorCode) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + ReturnValue = ErrorCode; + } + + SWMRGDoneReading(Plugin->AccountBrowserSO); + CloseHandle(File); + return 0; +} + +// Writes accounts to file +INT_PTR WriteAccountsToFileSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + + mir_cslock lck(csFileWritingCS); + HANDLE hFile = CreateFile((wchar_t *)lParam, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + return EACC_SYSTEM; + + return PerformAccountWriting(Plugin, hFile); +} + +INT_PTR FindAccountByNameSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + char *SearchedAccount = (char *)lParam; + CAccount *Finder; + + SWMRGWaitToRead(Plugin->AccountBrowserSO, INFINITE); + + for (Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) + if ((Finder->Name != nullptr) && (0 == mir_strcmp(SearchedAccount, Finder->Name))) + break; + + SWMRGDoneReading(Plugin->AccountBrowserSO); + return (INT_PTR)Finder; +} + +INT_PTR GetNextFreeAccountSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + CAccount *Finder; + + if (Plugin->FirstAccount == nullptr) { + Plugin->FirstAccount = (CAccount *)CallService(MS_YAMN_CREATEPLUGINACCOUNT, wParam, lParam); + return (INT_PTR)Plugin->FirstAccount; + } + for (Finder = Plugin->FirstAccount; Finder->Next != nullptr; Finder = Finder->Next); + Finder->Next = (CAccount *)CallService(MS_YAMN_CREATEPLUGINACCOUNT, wParam, lParam); + return (INT_PTR)Finder->Next; +} + +INT_PTR DeleteAccountSvc(WPARAM wParam, LPARAM lParam) +{ + //Deleting account works on these steps: + //1. set signal that account should stop activity (set event) + // setting this event we achieve, that any access to account is failed, + // so threads do not start any work with accounts (better saying threads of plugins should not start) + //2. wait to get write access to chained list of accounts + //3. we can write to chained list, so we change chain not to show to actual account + // now, any thread browsing list of accounts does not browse through actual account + // actual account seems to be hidden (it exists, but it is not in accounts chained list (chained list=queue)) + //Now, we should delete account from memory, BUT!!! + // Any thread can still be waked up and start asking account synchronizing object + // If account is deleted, asking about access to read account can throw memory exception (reading for + // a synchronizing object from memory, that was deleted) + //So, we cannot now delete account. We have to wait until we are sure no thread will be using account anymore + // (or to the end of Miranda, but problem is in allocated memory- it is allocated and Miranda is SMALLER, faster, easier, isn't it?) + // This deleting is achieved in 2 ways: + // We have event in UsingThreads synchronization objects. This event signals that no thread will use actual account + // 1. Any thread using account first increment UsingThread, so we know that account is used + // 2. If thread is about to close, it should decrement UsingThread + // 3. If thread creates another thread, that will use account, caller has to wait until the new thread does not + // increment UsingThreads (imagine that caller ends before the new thread set it: if no other thread is using + // account, account is automaticaly (decreasing UsingThreads) signaled as "not used" and we delete it. But then + // new thread is going to read account...). + //4. wait until UsingThread Event is signaled + //5. delete account from memory + + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + CAccount *Which = (CAccount *)lParam; + CAccount *Finder; + + //1. set stop signal + StopSignalFcn(Which); + WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_STOPACCOUNT, (WPARAM)Which, 0); + if (Plugin->Fcn->StopAccountFcnPtr != nullptr) + Plugin->Fcn->StopAccountFcnPtr(Which); + + //2. wait to get write access + SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); + + //3. remove from queue (chained list) + if (Plugin->FirstAccount == nullptr) { + SWMRGDoneWriting(Plugin->AccountBrowserSO); + return 0; + } + if (Plugin->FirstAccount == Which) { + Finder = Plugin->FirstAccount->Next; + Plugin->FirstAccount = Finder; + } + else { + for (Finder = Plugin->FirstAccount; Which != Finder->Next; Finder = Finder->Next); + Finder->Next = Finder->Next->Next; + } + + //leave write access + SWMRGDoneWriting(Plugin->AccountBrowserSO); + + //4. wait while event "UsingThread" is not signaled + // And what to do, if this event will be signaled in 1 hour? (Although it's paranoia, because we have sent "delete signal", so + // other threads do not start any new work with actual account) We will wait in blocked state? + // No, of course not. We will create new thread, that will wait and additionally remove our thread in background. + //5. So, the last point (deleting from memory) is performed in new DeleteAccountInBackground thread + + if ((Plugin->Fcn != nullptr) && (Plugin->Fcn->WriteAccountsFcnPtr != nullptr)) + Plugin->Fcn->WriteAccountsFcnPtr(); + CloseHandle(mir_forkthread(DeleteAccountInBackground, (void *)Which)); + + //Now, plugin can consider account as deleted, but plugin really can achieve deleting this account from memory when using + //event UsingThreads. + return 1; +} + +void __cdecl DeleteAccountInBackground(void *Value) +{ + CAccount *Which = (CAccount *)Value; + WaitForSingleObject(Which->UsingThreads->Event, INFINITE); + CallService(MS_YAMN_DELETEPLUGINACCOUNT, (WPARAM)Which, 0); +} + +int StopAccounts(HYAMNPROTOPLUGIN Plugin) +{ + CAccount *Finder; + + //1. wait to get write access + SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); + + for (Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { + //2. set stop signal + StopSignalFcn(Finder); + WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_STOPACCOUNT, (WPARAM)Finder, 0); + if (Plugin->Fcn->StopAccountFcnPtr != nullptr) + Plugin->Fcn->StopAccountFcnPtr(Finder); + } + + //leave write access + SWMRGDoneWriting(Plugin->AccountBrowserSO); + + //Now, account is stopped. It can be removed from memory... + return 1; +} + +int WaitForAllAccounts(HYAMNPROTOPLUGIN Plugin, BOOL GetAccountBrowserAccess) +{ + if (GetAccountBrowserAccess) { + //1. wait to get write access + SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); + } + for (CAccount *Finder = Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { + //2. wait for signal that account is not in use + WaitForSingleObject(Finder->UsingThreads->Event, INFINITE); + SetEvent(Finder->UsingThreads->Event); + } + if (GetAccountBrowserAccess) { + //leave write access + SWMRGDoneWriting(Plugin->AccountBrowserSO); + } + + return 1; +} + +int DeleteAccounts(HYAMNPROTOPLUGIN Plugin) +{ + //1. wait to get write access + SWMRGWaitToWrite(Plugin->AccountBrowserSO, INFINITE); + + WaitForAllAccounts(Plugin, FALSE); + + for (CAccount *Finder = Plugin->FirstAccount; Finder != nullptr;) { + CAccount *Next = Finder->Next; + DeletePluginAccountSvc((WPARAM)Finder, 0); + Finder = Next; + } + + //leave write access + SWMRGDoneWriting(Plugin->AccountBrowserSO); + return 1; +} + +void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value) +{ + if (Which == nullptr) + return; + + mir_cslock lck(csAccountStatusCS); + mir_wstrcpy(Value, Which->Status); +} + +void WINAPI SetStatusFcn(CAccount *Which, wchar_t *Value) +{ + if (Which != nullptr) { + mir_cslock lck(csAccountStatusCS); + mir_wstrcpy(Which->Status, Value); + } + + WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGESTATUS, (WPARAM)Which, 0); +} diff --git a/protocols/YAMN/src/browser/badconnect.cpp b/protocols/YAMN/src/browser/badconnect.cpp index 5f9118eb18..d871fa2eea 100644 --- a/protocols/YAMN/src/browser/badconnect.cpp +++ b/protocols/YAMN/src/browser/badconnect.cpp @@ -1,251 +1,251 @@ -/* - * This code implements window handling (connection error) - * - * (c) majvan 2002,2004 - */ - -#include "../stdafx.h" - -#define BADCONNECTTITLE LPGEN("%s - connection error") -#define BADCONNECTMSG LPGEN("An error occurred. Error code: %d")//is in use? - -//-------------------------------------------------------------------------------------------------- - -LRESULT CALLBACK BadConnectPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_COMMAND: - // if clicked and it's new mail popup window - if ((HIWORD(wParam) == STN_CLICKED) && (PUGetPluginData(hWnd))) { - PROCESS_INFORMATION pi; - STARTUPINFOW si; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - CAccount *ActualAccount = (CAccount *)PUGetPluginData(hWnd); - - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - if (ActualAccount->BadConnectN.App != nullptr) { - wchar_t *Command; - if (ActualAccount->BadConnectN.AppParam != nullptr) - Command = new wchar_t[mir_wstrlen(ActualAccount->BadConnectN.App) + mir_wstrlen(ActualAccount->BadConnectN.AppParam) + 6]; - else - Command = new wchar_t[mir_wstrlen(ActualAccount->BadConnectN.App) + 6]; - - if (Command != nullptr) { - mir_wstrcpy(Command, L"\""); - mir_wstrcat(Command, ActualAccount->BadConnectN.App); - mir_wstrcat(Command, L"\" "); - if (ActualAccount->BadConnectN.AppParam != nullptr) - mir_wstrcat(Command, ActualAccount->BadConnectN.AppParam); - CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); - delete[] Command; - } - } - - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - - PUDeletePopup(hWnd); - } - break; - - case UM_FREEPLUGINDATA: - //Here we'd free our own data, if we had it. - return FALSE; - - case UM_INITPOPUP: - //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. - break; - case WM_CONTEXTMENU: - PUDeletePopup(hWnd); - break; - } - return DefWindowProc(hWnd, msg, wParam, lParam); -} - -INT_PTR CALLBACK DlgProcYAMNBadConnection(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - { - BOOL ShowPopup, ShowMsg, ShowIco; - CAccount *ActualAccount; - uint32_t ErrorCode; - char *TitleStrA; - char *Message1A = nullptr; - wchar_t *Message1W = nullptr; - POPUPDATAW BadConnectPopup = {}; - - ActualAccount = ((struct BadConnectionParam *)lParam)->account; - ErrorCode = ((struct BadConnectionParam *)lParam)->errcode; - - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) - return FALSE; - - int size = (int)(mir_strlen(ActualAccount->Name) + mir_strlen(Translate(BADCONNECTTITLE))); - TitleStrA = new char[size]; - mir_snprintf(TitleStrA, size, Translate(BADCONNECTTITLE), ActualAccount->Name); - - ShowPopup = ActualAccount->BadConnectN.Flags & YAMN_ACC_POP; - ShowMsg = ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG; - ShowIco = ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO; - - if (ShowPopup) { - BadConnectPopup.lchIcon = g_plugin.getIcon(IDI_BADCONNECT); - BadConnectPopup.colorBack = ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC ? ActualAccount->BadConnectN.PopupB : GetSysColor(COLOR_BTNFACE); - BadConnectPopup.colorText = ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC ? ActualAccount->BadConnectN.PopupT : GetSysColor(COLOR_WINDOWTEXT); - BadConnectPopup.iSeconds = ActualAccount->BadConnectN.PopupTime; - - BadConnectPopup.PluginWindowProc = BadConnectPopupProc; - BadConnectPopup.PluginData = ActualAccount; - mir_wstrncpy(BadConnectPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(BadConnectPopup.lpwzContactName)); - } - - if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr != nullptr) { - Message1W = ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr(ErrorCode); - SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); - wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); - if (ShowPopup) - PUAddPopupW(&BadConnectPopup); - } - else if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->GetErrorStringAFcnPtr != nullptr) { - Message1W = ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr(ErrorCode); - SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); - wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); - if (ShowPopup) - PUAddPopupW(&BadConnectPopup); - } - else { - Message1W = TranslateT("Unknown error"); - SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); - wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); - if (ShowPopup) - PUAddPopupW(&BadConnectPopup); - } - - if (!ShowMsg && !ShowIco) - DestroyWindow(hDlg); - - ReadDoneFcn(ActualAccount->AccountAccessSO); - - SetWindowTextA(hDlg, TitleStrA); - delete[] TitleStrA; - if (Message1A != nullptr) - delete[] Message1A; - if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr != nullptr && Message1A != nullptr) - ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr(Message1A); - if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr != nullptr && Message1W != nullptr) - ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr(Message1W); - return 0; - } - case WM_DESTROY: - { - NOTIFYICONDATA nid; - - memset(&nid, 0, sizeof(NOTIFYICONDATA)); - nid.cbSize = sizeof(NOTIFYICONDATA); - nid.hWnd = hDlg; - nid.uID = 0; - Shell_NotifyIcon(NIM_DELETE, &nid); - PostQuitMessage(0); - break; - } - case WM_YAMN_NOTIFYICON: - switch (lParam) { - case WM_LBUTTONDBLCLK: - ShowWindow(hDlg, SW_SHOWNORMAL); - SetForegroundWindow(hDlg); - break; - } - return 0; - case WM_CHAR: - switch ((wchar_t)wParam) { - case 27: - case 13: - DestroyWindow(hDlg); - break; - } - break; - case WM_SYSCOMMAND: - switch (wParam) { - case SC_CLOSE: - DestroyWindow(hDlg); - } - break; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_BTNOK: - DestroyWindow(hDlg); - } - break; - } - return 0; -} - -void __cdecl BadConnection(void *Param) -{ - MSG msg; - HWND hBadConnect; - CAccount *ActualAccount; - - struct BadConnectionParam MyParam = *(struct BadConnectionParam *)Param; - ActualAccount = MyParam.account; - - SCIncFcn(ActualAccount->UsingThreads); - - // we will not use params in stack anymore - SetEvent(MyParam.ThreadRunningEV); - - __try { - hBadConnect = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_DLGBADCONNECT), nullptr, DlgProcYAMNBadConnection, (LPARAM)&MyParam); - Window_SetIcon_IcoLib(hBadConnect, g_plugin.getIconHandle(IDI_BADCONNECT)); - - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) - __leave; - - if (ActualAccount->BadConnectN.Flags & YAMN_ACC_SND) - Skin_PlaySound(YAMN_CONNECTFAILSOUND); - - if (ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) - ShowWindow(hBadConnect, SW_SHOWNORMAL); - - if (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) { - NOTIFYICONDATA nid = {}; - nid.cbSize = sizeof(nid); - nid.hWnd = hBadConnect; - nid.hIcon = g_plugin.getIcon(IDI_BADCONNECT); - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - nid.uCallbackMessage = WM_YAMN_NOTIFYICON; - mir_snwprintf(nid.szTip, L"%S%s", ActualAccount->Name, TranslateT(" - connection error")); - Shell_NotifyIcon(NIM_ADD, &nid); - } - - ReadDoneFcn(ActualAccount->AccountAccessSO); - - UpdateWindow(hBadConnect); - while (GetMessage(&msg, nullptr, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - // now, write to file. Why? Because we want to write when was new mail last checked - if ((ActualAccount->Plugin->Fcn != nullptr) && (ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr != nullptr) && ActualAccount->AbleToWork) - ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr(); - } - __finally { - SCDecFcn(ActualAccount->UsingThreads); - } -} - -int RunBadConnection(CAccount *acc, UINT_PTR iErrorCode, void *pUserInfo) -{ - BadConnectionParam param = {CreateEvent(nullptr, FALSE, FALSE, nullptr), acc, iErrorCode, pUserInfo}; - - HANDLE NewThread = mir_forkthread(BadConnection, ¶m); - if (nullptr == NewThread) - return 0; - - WaitForSingleObject(param.ThreadRunningEV, INFINITE); - CloseHandle(param.ThreadRunningEV); - return 1; -} +/* + * This code implements window handling (connection error) + * + * (c) majvan 2002,2004 + */ + +#include "../stdafx.h" + +#define BADCONNECTTITLE LPGEN("%s - connection error") +#define BADCONNECTMSG LPGEN("An error occurred. Error code: %d")//is in use? + +//-------------------------------------------------------------------------------------------------- + +LRESULT CALLBACK BadConnectPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_COMMAND: + // if clicked and it's new mail popup window + if ((HIWORD(wParam) == STN_CLICKED) && (PUGetPluginData(hWnd))) { + PROCESS_INFORMATION pi; + STARTUPINFOW si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + CAccount *ActualAccount = (CAccount *)PUGetPluginData(hWnd); + + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + if (ActualAccount->BadConnectN.App != nullptr) { + wchar_t *Command; + if (ActualAccount->BadConnectN.AppParam != nullptr) + Command = new wchar_t[mir_wstrlen(ActualAccount->BadConnectN.App) + mir_wstrlen(ActualAccount->BadConnectN.AppParam) + 6]; + else + Command = new wchar_t[mir_wstrlen(ActualAccount->BadConnectN.App) + 6]; + + if (Command != nullptr) { + mir_wstrcpy(Command, L"\""); + mir_wstrcat(Command, ActualAccount->BadConnectN.App); + mir_wstrcat(Command, L"\" "); + if (ActualAccount->BadConnectN.AppParam != nullptr) + mir_wstrcat(Command, ActualAccount->BadConnectN.AppParam); + CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + delete[] Command; + } + } + + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + + PUDeletePopup(hWnd); + } + break; + + case UM_FREEPLUGINDATA: + //Here we'd free our own data, if we had it. + return FALSE; + + case UM_INITPOPUP: + //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. + break; + case WM_CONTEXTMENU: + PUDeletePopup(hWnd); + break; + } + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +INT_PTR CALLBACK DlgProcYAMNBadConnection(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + BOOL ShowPopup, ShowMsg, ShowIco; + CAccount *ActualAccount; + uint32_t ErrorCode; + char *TitleStrA; + char *Message1A = nullptr; + wchar_t *Message1W = nullptr; + POPUPDATAW BadConnectPopup = {}; + + ActualAccount = ((struct BadConnectionParam *)lParam)->account; + ErrorCode = ((struct BadConnectionParam *)lParam)->errcode; + + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) + return FALSE; + + int size = (int)(mir_strlen(ActualAccount->Name) + mir_strlen(Translate(BADCONNECTTITLE))); + TitleStrA = new char[size]; + mir_snprintf(TitleStrA, size, Translate(BADCONNECTTITLE), ActualAccount->Name); + + ShowPopup = ActualAccount->BadConnectN.Flags & YAMN_ACC_POP; + ShowMsg = ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG; + ShowIco = ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO; + + if (ShowPopup) { + BadConnectPopup.lchIcon = g_plugin.getIcon(IDI_BADCONNECT); + BadConnectPopup.colorBack = ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC ? ActualAccount->BadConnectN.PopupB : GetSysColor(COLOR_BTNFACE); + BadConnectPopup.colorText = ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC ? ActualAccount->BadConnectN.PopupT : GetSysColor(COLOR_WINDOWTEXT); + BadConnectPopup.iSeconds = ActualAccount->BadConnectN.PopupTime; + + BadConnectPopup.PluginWindowProc = BadConnectPopupProc; + BadConnectPopup.PluginData = ActualAccount; + mir_wstrncpy(BadConnectPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(BadConnectPopup.lpwzContactName)); + } + + if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr != nullptr) { + Message1W = ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr(ErrorCode); + SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); + wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); + if (ShowPopup) + PUAddPopupW(&BadConnectPopup); + } + else if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->GetErrorStringAFcnPtr != nullptr) { + Message1W = ActualAccount->Plugin->Fcn->GetErrorStringWFcnPtr(ErrorCode); + SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); + wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); + if (ShowPopup) + PUAddPopupW(&BadConnectPopup); + } + else { + Message1W = TranslateT("Unknown error"); + SetDlgItemText(hDlg, IDC_STATICMSG, Message1W); + wcsncpy_s(BadConnectPopup.lpwzText, Message1W, _TRUNCATE); + if (ShowPopup) + PUAddPopupW(&BadConnectPopup); + } + + if (!ShowMsg && !ShowIco) + DestroyWindow(hDlg); + + ReadDoneFcn(ActualAccount->AccountAccessSO); + + SetWindowTextA(hDlg, TitleStrA); + delete[] TitleStrA; + if (Message1A != nullptr) + delete[] Message1A; + if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr != nullptr && Message1A != nullptr) + ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr(Message1A); + if (ActualAccount->Plugin->Fcn != nullptr && ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr != nullptr && Message1W != nullptr) + ActualAccount->Plugin->Fcn->DeleteErrorStringFcnPtr(Message1W); + return 0; + } + case WM_DESTROY: + { + NOTIFYICONDATA nid; + + memset(&nid, 0, sizeof(NOTIFYICONDATA)); + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hDlg; + nid.uID = 0; + Shell_NotifyIcon(NIM_DELETE, &nid); + PostQuitMessage(0); + break; + } + case WM_YAMN_NOTIFYICON: + switch (lParam) { + case WM_LBUTTONDBLCLK: + ShowWindow(hDlg, SW_SHOWNORMAL); + SetForegroundWindow(hDlg); + break; + } + return 0; + case WM_CHAR: + switch ((wchar_t)wParam) { + case 27: + case 13: + DestroyWindow(hDlg); + break; + } + break; + case WM_SYSCOMMAND: + switch (wParam) { + case SC_CLOSE: + DestroyWindow(hDlg); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_BTNOK: + DestroyWindow(hDlg); + } + break; + } + return 0; +} + +void __cdecl BadConnection(void *Param) +{ + MSG msg; + HWND hBadConnect; + CAccount *ActualAccount; + + struct BadConnectionParam MyParam = *(struct BadConnectionParam *)Param; + ActualAccount = MyParam.account; + + SCIncFcn(ActualAccount->UsingThreads); + + // we will not use params in stack anymore + SetEvent(MyParam.ThreadRunningEV); + + __try { + hBadConnect = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_DLGBADCONNECT), nullptr, DlgProcYAMNBadConnection, (LPARAM)&MyParam); + Window_SetIcon_IcoLib(hBadConnect, g_plugin.getIconHandle(IDI_BADCONNECT)); + + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) + __leave; + + if (ActualAccount->BadConnectN.Flags & YAMN_ACC_SND) + Skin_PlaySound(YAMN_CONNECTFAILSOUND); + + if (ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) + ShowWindow(hBadConnect, SW_SHOWNORMAL); + + if (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) { + NOTIFYICONDATA nid = {}; + nid.cbSize = sizeof(nid); + nid.hWnd = hBadConnect; + nid.hIcon = g_plugin.getIcon(IDI_BADCONNECT); + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = WM_YAMN_NOTIFYICON; + mir_snwprintf(nid.szTip, L"%S%s", ActualAccount->Name, TranslateT(" - connection error")); + Shell_NotifyIcon(NIM_ADD, &nid); + } + + ReadDoneFcn(ActualAccount->AccountAccessSO); + + UpdateWindow(hBadConnect); + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // now, write to file. Why? Because we want to write when was new mail last checked + if ((ActualAccount->Plugin->Fcn != nullptr) && (ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr != nullptr) && ActualAccount->AbleToWork) + ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr(); + } + __finally { + SCDecFcn(ActualAccount->UsingThreads); + } +} + +int RunBadConnection(CAccount *acc, UINT_PTR iErrorCode, void *pUserInfo) +{ + BadConnectionParam param = {CreateEvent(nullptr, FALSE, FALSE, nullptr), acc, iErrorCode, pUserInfo}; + + HANDLE NewThread = mir_forkthread(BadConnection, ¶m); + if (nullptr == NewThread) + return 0; + + WaitForSingleObject(param.ThreadRunningEV, INFINITE); + CloseHandle(param.ThreadRunningEV); + return 1; +} diff --git a/protocols/YAMN/src/browser/browser.h b/protocols/YAMN/src/browser/browser.h index 6707d50621..0cb3f1a248 100644 --- a/protocols/YAMN/src/browser/browser.h +++ b/protocols/YAMN/src/browser/browser.h @@ -1,38 +1,38 @@ -#ifndef __MAILBROWSER_H -#define __MAILBROWSER_H - -typedef struct MailBrowserWinParam -{ -#define YAMN_MAILBROWSERVERSION 1 - HANDLE ThreadRunningEV; - CAccount *account; - uint32_t nflags; //flags YAMN_ACC_??? when new mails - uint32_t nnflags; //flags YAMN_ACC_??? when no new mails - void *Param; -} YAMN_MAILBROWSERPARAM,*PYAMN_MAILBROWSERPARAM; - -typedef struct MailShowMsgWinParam -{ - HANDLE ThreadRunningEV; - CAccount *account; - HYAMNMAIL mail; -} YAMN_MAILSHOWPARAM, *PYAMN_MAILSHOWPARAM; - -typedef struct NoNewMailParam -{ -#define YAMN_NONEWMAILVERSION 1 - HANDLE ThreadRunningEV; - CAccount *account; - uint32_t flags; - void *Param; -} YAMN_NONEWMAILPARAM,*PYAMN_NONEWMAILPARAM; - -struct BadConnectionParam -{ - HANDLE ThreadRunningEV; - CAccount *account; - UINT_PTR errcode; - void *Param; -}; - -#endif +#ifndef __MAILBROWSER_H +#define __MAILBROWSER_H + +typedef struct MailBrowserWinParam +{ +#define YAMN_MAILBROWSERVERSION 1 + HANDLE ThreadRunningEV; + CAccount *account; + uint32_t nflags; //flags YAMN_ACC_??? when new mails + uint32_t nnflags; //flags YAMN_ACC_??? when no new mails + void *Param; +} YAMN_MAILBROWSERPARAM,*PYAMN_MAILBROWSERPARAM; + +typedef struct MailShowMsgWinParam +{ + HANDLE ThreadRunningEV; + CAccount *account; + HYAMNMAIL mail; +} YAMN_MAILSHOWPARAM, *PYAMN_MAILSHOWPARAM; + +typedef struct NoNewMailParam +{ +#define YAMN_NONEWMAILVERSION 1 + HANDLE ThreadRunningEV; + CAccount *account; + uint32_t flags; + void *Param; +} YAMN_NONEWMAILPARAM,*PYAMN_NONEWMAILPARAM; + +struct BadConnectionParam +{ + HANDLE ThreadRunningEV; + CAccount *account; + UINT_PTR errcode; + void *Param; +}; + +#endif diff --git a/protocols/YAMN/src/browser/mailbrowser.cpp b/protocols/YAMN/src/browser/mailbrowser.cpp index 1d62ad00dc..7b1b68e35a 100644 --- a/protocols/YAMN/src/browser/mailbrowser.cpp +++ b/protocols/YAMN/src/browser/mailbrowser.cpp @@ -1,2247 +1,2247 @@ -/* - * This code implements window handling (new mail) - * - * (c) majvan 2002-2004 - */ - /* There can be problems when compiling this file, because in this file - * we are using both unicode and no-unicode functions and compiler does not - * like it in one file - * When you got errors, try to comment the #define and compile, then - * put it back to uncommented and compile again :) - */ - -#include "../stdafx.h" - -#define TIMER_FLASHING 0x09061979 -#define MAILBROWSER_MINXSIZE 200 //min size of mail browser window -#define MAILBROWSER_MINYSIZE 130 - -#define MAILBROWSERTITLE LPGEN("%s - %d new mail messages, %d total") - -void __cdecl ShowEmailThread(void *Param); - -//-------------------------------------------------------------------------------------------------- -char *s_MonthNames[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; -bool bDate = false, bSub = false, bSize = false, bFrom = false; -int PosX = 0, PosY = 0, SizeX = 460, SizeY = 100; -int HeadSizeX = 0x2b2, HeadSizeY = 0x0b5, HeadPosX = 100, HeadPosY = 100; -int HeadSplitPos = 250; // per-mils of the size -static int FromWidth = 250, SubjectWidth = 280, SizeWidth = 50, SizeDate = 205; -unsigned char optDateTime = (SHOWDATELONG | SHOWDATENOTODAY); - -struct CMailNumbersSub -{ - int Total; //any mail - int New; //uses YAMN_MSG_NEW flag - int UnSeen; //uses YAMN_MSG_UNSEEN flag - // int Browser; //uses YAMN_MSG_BROWSER flag - int BrowserUC; //uses YAMN_MSG_BROWSER flag and YAMN_MSG_UNSEEN flag - int Display; //uses YAMN_MSG_DISPLAY flag - int DisplayTC; //uses YAMN_MSG_DISPLAY flag and YAMN_MSG_DISPLAYC flag - int DisplayUC; //uses YAMN_MSG_DISPLAY flag and YAMN_MSG_DISPLAYC flag and YAMN_MSG_UNSEEN flag - int Popup; //uses YAMN_MSG_POPUP flag - int PopupTC; //uses YAMN_MSG_POPUPC flag - int PopupNC; //uses YAMN_MSG_POPUPC flag and YAMN_MSG_NEW flag - int PopupRun; //uses YAMN_MSG_POPUP flag and YAMN_MSG_NEW flag - int PopupSL2NC; //uses YAMN_MSG_SPAML2 flag and YAMN_MSG_NEW flag - int PopupSL3NC; //uses YAMN_MSG_SPAML3 flag and YAMN_MSG_NEW flag - // int SysTray; //uses YAMN_MSG_SYSTRAY flag - int SysTrayUC; //uses YAMN_MSG_SYSTRAY flag and YAMN_MSG_UNSEEN flag - // int Sound; //uses YAMN_MSG_SOUND flag - int SoundNC; //uses YAMN_MSG_SOUND flag and YAMN_MSG_NEW flag - // int App; //uses YAMN_MSG_APP flag - int AppNC; //uses YAMN_MSG_APP flag and YAMN_MSG_NEW flag - int EventNC; //uses YAMN_MSG_NEVENT flag and YAMN_MSG_NEW flag -}; - -struct CMailNumbers -{ - struct CMailNumbersSub Real; - struct CMailNumbersSub Virtual; -}; - -struct CMailWinUserInfo -{ - CAccount *Account; - int TrayIconState; - BOOL UpdateMailsMessagesAccess; - BOOL Seen; - BOOL RunFirstTime; -}; - -struct CChangeContent -{ - uint32_t nflags; - uint32_t nnflags; -}; - -struct CUpdateMails -{ - struct CChangeContent *Flags; - BOOL Waiting; - HANDLE Copied; -}; - -struct CSortList -{ - HWND hDlg; - int iSubItem; -}; - -// Retrieves CAccount *, whose mails are displayed in ListMails -// hLM- handle of dialog window -// returns handle of account -inline CAccount *GetWindowAccount(HWND hDialog); - -// Looks to mail flags and increment mail counter (e.g. if mail is new, increments the new mail counter -// msgq- mail, which increments the counters -// MN- counnters structure -void IncrementMailCounters(HYAMNMAIL msgq, struct CMailNumbers *MN); - -enum -{ - UPDATE_FAIL = 0, //function failed - UPDATE_NONE, //none update has been performed - UPDATE_OK, //some changes occured, update performed -}; - -// Just looks for mail changes in account and update the mail browser window -// hDlg- dialog handle -// ActualAccount- account handle -// nflags- flags what to do when new mail arrives -// nnflags- flags what to do when no new mail arrives -// returns one of UPDATE_XXX value(not implemented yet) -int UpdateMails(HWND hDlg, CAccount *ActualAccount, uint32_t nflags, uint32_t nnflags); - -// When new mail occurs, shows window, plays sound, runs application... -// hDlg- dialog handle. Dialog of mailbrowser is already created and actions are performed over this window -// ActualAccount- handle of account, whose mails are to be notified -// MN- statistics of mails in account -// nflags- what to do or not to do (e.g. to show mailbrowser window or prohibit to show) -// nflags- flags what to do when new mail arrives -// nnflags- flags what to do when no new mail arrives -void DoMailActions(HWND hDlg, CAccount *ActualAccount, struct CMailNumbers *MN, uint32_t nflags, uint32_t nnflags); - -// Looks for items in mailbrowser and if they were deleted, delete them from browser window -// hListView- handle of listview window -// ActualAccount- handle of account, whose mails are show -// MailNumbers- pointer to structure, in which function stores numbers of mails with some property -// returns one of UPDATE_XXX value (not implemented yet) -int ChangeExistingMailStatus(HWND hListView, CAccount *ActualAccount); - -// Adds new mails to ListView and if any new, shows multi popup (every new message is new popup window created by popup plugin) -// hListView- handle of listview window -// ActualAccount- handle of account, whose mails are show -// NewMailPopup- pointer to prepared structure for popup plugin, can be NULL if no popup show -// MailNumbers- pointer to structure, in which function stores numbers of mails with some property -// nflags- flags what to do when new mail arrives -// returns one of UPDATE_XXX value (not implemented yet) -int AddNewMailsToListView(HWND hListView, CAccount *ActualAccount, uint32_t nflags); - -// Window callback procedure for popup window (created by popup plugin) -LRESULT CALLBACK NewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// Window callback procedure for popup window (created by popup plugin) -LRESULT CALLBACK NoNewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// Dialog callback procedure for mail browser -INT_PTR CALLBACK DlgProcYAMNMailBrowser(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam); - -// MailBrowser thread function creates window if needed, tray icon and plays sound -void __cdecl MailBrowser(void *Param); - -LRESULT CALLBACK ListViewSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// Runs mail browser in new thread -INT_PTR RunMailBrowserSvc(WPARAM, LPARAM); - -#define YAMN_BROWSER_SHOWPOPUP 0x01 - -// list view items' order criteria -#define LVORDER_NOORDER -1 -#define LVORDER_STRING 0 -#define LVORDER_NUMERIC 1 -#define LVORDER_DATETIME 2 - -// list view order direction -#define LVORDER_ASCENDING 1 -#define LVORDER_NONE 0 -#define LVORDER_DESCENDING -1 - -// list view sort type -#define LVSORTPRIORITY_NONE -1 - -// List view column info. -typedef struct _SAMPLELISTVIEWCOLUMN -{ - UINT uCXCol; // index - int nSortType; // sorting type (STRING = 0, NUMERIC, DATE, DATETIME) - int nSortOrder; // sorting order (ASCENDING = -1, NONE, DESCENDING) - int nPriority; // sort priority (-1 for none, 0, 1, ..., nColumns - 1 maximum) - wchar_t lpszName[128]; // column name -} SAMPLELISTVIEWCOLUMN; - -// Compare priority -typedef struct _LVCOMPAREINFO -{ - int iIdx; // Index - int iPriority; // Priority -} LVCOMPAREINFO, *LPLVCOMPAREINFO; - -//-------------------------------------------------------------------------------------------------- - -LPARAM readItemLParam(HWND hwnd, uint32_t iItem) -{ - LVITEM item; - item.mask = LVIF_PARAM; - item.iItem = iItem; - item.iSubItem = 0; - SendMessage(hwnd, LVM_GETITEM, 0, (LPARAM)&item); - return item.lParam; -} - -inline CAccount *GetWindowAccount(HWND hDlg) -{ - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - - return (mwui == nullptr) ? nullptr : mwui->Account; -} - -void IncrementMailCounters(HYAMNMAIL msgq, struct CMailNumbers *MN) -{ - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.Total++; - else - MN->Real.Total++; - - if (msgq->Flags & YAMN_MSG_NEW) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.New++; - else - MN->Real.New++; - if (msgq->Flags & YAMN_MSG_UNSEEN) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.UnSeen++; - else - MN->Real.UnSeen++; - if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_BROWSER)) == (YAMN_MSG_UNSEEN | YAMN_MSG_BROWSER)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.BrowserUC++; - else - MN->Real.BrowserUC++; - if (msgq->Flags & YAMN_MSG_DISPLAY) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.Display++; - else - MN->Real.Display++; - if ((msgq->Flags & (YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) == (YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.DisplayTC++; - else - MN->Real.DisplayTC++; - if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) == (YAMN_MSG_UNSEEN | YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.DisplayUC++; - else - MN->Real.DisplayUC++; - if (msgq->Flags & YAMN_MSG_POPUP) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.Popup++; - else - MN->Real.Popup++; - if ((msgq->Flags & YAMN_MSG_POPUPC) == YAMN_MSG_POPUPC) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.PopupTC++; - else - MN->Real.PopupTC++; - if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_POPUPC)) == (YAMN_MSG_NEW | YAMN_MSG_POPUPC)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.PopupNC++; - else - MN->Real.PopupNC++; - if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_POPUP)) == (YAMN_MSG_NEW | YAMN_MSG_POPUP)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.PopupRun++; - else - MN->Real.PopupRun++; - if ((msgq->Flags & YAMN_MSG_NEW) && YAMN_MSG_SPAML(msgq->Flags, YAMN_MSG_SPAML2)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.PopupSL2NC++; - else - MN->Real.PopupSL2NC++; - if ((msgq->Flags & YAMN_MSG_NEW) && YAMN_MSG_SPAML(msgq->Flags, YAMN_MSG_SPAML3)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.PopupSL3NC++; - else - MN->Real.PopupSL3NC++; - /* if (msgq->MailData->Flags & YAMN_MSG_SYSTRAY) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.SysTray++; - else - MN->Real.SysTray++; - */ if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_SYSTRAY)) == (YAMN_MSG_UNSEEN | YAMN_MSG_SYSTRAY)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.SysTrayUC++; - else - MN->Real.SysTrayUC++; - /* if (msgq->MailData->Flags & YAMN_MSG_SOUND) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.Sound++; - else - MN->Real.Sound++; - */ if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_SOUND)) == (YAMN_MSG_NEW | YAMN_MSG_SOUND)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.SoundNC++; - else - MN->Real.SoundNC++; - /* if (msgq->MailData->Flags & YAMN_MSG_APP) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.App++; - else - MN->Real.App++; - */ if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_APP)) == (YAMN_MSG_NEW | YAMN_MSG_APP)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.AppNC++; - else - MN->Real.AppNC++; - if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_NEVENT)) == (YAMN_MSG_NEW | YAMN_MSG_NEVENT)) - if (msgq->Flags & YAMN_MSG_VIRTUAL) - MN->Virtual.EventNC++; - else - MN->Real.EventNC++; -} - -int UpdateMails(HWND hDlg, CAccount *ActualAccount, uint32_t nflags, uint32_t nnflags) -{ - struct CMailNumbers MN; - - BOOL Loaded; - BOOL RunMailBrowser, RunPopups; - - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - //now we ensure read access for account and write access for its mails - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { - PostMessage(hDlg, WM_DESTROY, 0, 0); - return UPDATE_FAIL; - } - - if (WAIT_OBJECT_0 != WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - PostMessage(hDlg, WM_DESTROY, 0, 0); - return UPDATE_FAIL; - } - - memset(&MN, 0, sizeof(MN)); - - for (HYAMNMAIL msgq = (HYAMNMAIL)ActualAccount->Mails; msgq != nullptr; msgq = msgq->Next) { - if (!LoadedMailData(msgq)) //check if mail is already in memory - { - Loaded = false; - if (nullptr == LoadMailData(msgq)) //if we could not load mail to memory, consider this mail deleted and do not display it - continue; - } - else - Loaded = true; - - IncrementMailCounters(msgq, &MN); - - if (!Loaded) - UnloadMailData(msgq); //do not keep data for mail in memory - } - - if (mwui != nullptr) - mwui->UpdateMailsMessagesAccess = TRUE; - - //Now we are going to check if extracting data from mail headers are needed. - //If popups will be displayed or mailbrowser window - if ((((mwui != nullptr) && !(mwui->RunFirstTime)) && - ( - ((nnflags & YAMN_ACC_MSGP) && !(MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || - ((nflags & YAMN_ACC_MSGP) && (MN.Real.BrowserUC + MN.Virtual.BrowserUC)) - ) - ) || //if mail window was displayed before and flag YAMN_ACC_MSGP is set - ((nnflags & YAMN_ACC_MSG) && !(MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || //if needed to run mailbrowser when no unseen and no unseen mail found - ((nflags & YAMN_ACC_MSG) && (MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || //if unseen mails found, we sure run mailbrowser - ((nflags & YAMN_ACC_ICO) && (MN.Real.SysTrayUC + MN.Virtual.SysTrayUC)) - ) //if needed to run systray - RunMailBrowser = TRUE; - else - RunMailBrowser = FALSE; - - // if some popups with mails are needed to show - if ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (MN.Real.PopupNC + MN.Virtual.PopupNC)) - RunPopups = TRUE; - else RunPopups = FALSE; - - if (RunMailBrowser) - ChangeExistingMailStatus(GetDlgItem(hDlg, IDC_LISTMAILS), ActualAccount); - if (RunMailBrowser || RunPopups) - AddNewMailsToListView(hDlg == nullptr ? nullptr : GetDlgItem(hDlg, IDC_LISTMAILS), ActualAccount, nflags); - - if (RunMailBrowser) { - size_t len = mir_strlen(ActualAccount->Name) + mir_strlen(Translate(MAILBROWSERTITLE)) + 10; //+10 chars for numbers - char *TitleStrA = new char[len]; - wchar_t *TitleStrW = new wchar_t[len]; - - mir_snprintf(TitleStrA, len, Translate(MAILBROWSERTITLE), ActualAccount->Name, MN.Real.DisplayUC + MN.Virtual.DisplayUC, MN.Real.Display + MN.Virtual.Display); - MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, TitleStrA, -1, TitleStrW, (int)mir_strlen(TitleStrA) + 1); - SetWindowTextW(hDlg, TitleStrW); - delete[] TitleStrA; - delete[] TitleStrW; - } - - DoMailActions(hDlg, ActualAccount, &MN, nflags, nnflags); - - SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_NEW, 0, YAMN_MSG_NEW, YAMN_FLAG_REMOVE); //rempve the new flag - if (!RunMailBrowser) - SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_UNSEEN, YAMN_MSG_STAYUNSEEN, YAMN_MSG_UNSEEN, YAMN_FLAG_REMOVE); //remove the unseen flag when it was not displayed and it has not "stay unseen" flag set - - if (mwui != nullptr) { - mwui->UpdateMailsMessagesAccess = FALSE; - mwui->RunFirstTime = FALSE; - } - - WriteDoneFcn(ActualAccount->MessagesAccessSO); - ReadDoneFcn(ActualAccount->AccountAccessSO); - - if (RunMailBrowser) - UpdateWindow(GetDlgItem(hDlg, IDC_LISTMAILS)); - else if (hDlg != nullptr) - DestroyWindow(hDlg); - - return 1; -} - -int ChangeExistingMailStatus(HWND hListView, CAccount *ActualAccount) -{ - LVITEM item; - HYAMNMAIL mail, msgq; - - int in = ListView_GetItemCount(hListView); - item.mask = LVIF_PARAM; - - for (int i = 0; i < in; i++) { - item.iItem = i; - item.iSubItem = 0; - if (TRUE == ListView_GetItem(hListView, &item)) - mail = (HYAMNMAIL)item.lParam; - else - continue; - for (msgq = (HYAMNMAIL)ActualAccount->Mails; (msgq != nullptr) && (msgq != mail); msgq = msgq->Next); //found the same mail in account queue - if (msgq == nullptr) //if mail was not found - if (TRUE == ListView_DeleteItem(hListView, i)) { - in--; i--; - continue; - } - } - - return TRUE; -} - -void MimeDateToLocalizedDateTime(char *datein, wchar_t *dateout, int lendateout); -int AddNewMailsToListView(HWND hListView, CAccount *ActualAccount, uint32_t nflags) -{ - wchar_t *FromStr; - wchar_t SizeStr[20]; - wchar_t LocalDateStr[128]; - - LVITEMW item; - LVFINDINFO fi; - - int foundi = 0, lfoundi = 0; - struct CHeader UnicodeHeader; - BOOL Loaded, Extracted, FromStrNew = FALSE; - - memset(&item, 0, sizeof(item)); - memset(&UnicodeHeader, 0, sizeof(UnicodeHeader)); - - if (hListView != nullptr) { - item.mask = LVIF_TEXT | LVIF_PARAM; - item.iItem = 0; - memset(&fi, 0, sizeof(fi)); - fi.flags = LVFI_PARAM; //let's go search item by lParam number - lfoundi = 0; - } - - POPUPDATAW NewMailPopup = {}; - NewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; - NewMailPopup.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); - if (nflags & YAMN_ACC_POPC) { - NewMailPopup.colorBack = ActualAccount->NewMailN.PopupB; - NewMailPopup.colorText = ActualAccount->NewMailN.PopupT; - } - else { - NewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); - NewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - NewMailPopup.iSeconds = ActualAccount->NewMailN.PopupTime; - - NewMailPopup.PluginWindowProc = NewMailPopupProc; - NewMailPopup.PluginData = nullptr; //it's new mail popup - - for (HYAMNMAIL msgq = (HYAMNMAIL)ActualAccount->Mails; msgq != nullptr; msgq = msgq->Next, lfoundi++) { - // now we hide mail pointer to item's lParam member. We can later use it to retrieve mail datas - - Extracted = FALSE; FromStr = nullptr; FromStrNew = FALSE; - - if (hListView != nullptr) { - fi.lParam = (LPARAM)msgq; - if (-1 != (foundi = ListView_FindItem(hListView, -1, &fi))) { // if mail is already in window - lfoundi = foundi; - continue; // do not insert any item - } - - item.iItem = lfoundi; // insert after last found item - item.lParam = (LPARAM)msgq; - } - - if (!LoadedMailData(msgq)) { // check if mail is already in memory - Loaded = false; - if (nullptr == LoadMailData(msgq)) //if we could not load mail to memory, consider this mail deleted and do not display it - continue; - } - else Loaded = true; - - if (((hListView != nullptr) && (msgq->Flags & YAMN_MSG_DISPLAY)) || - ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (msgq->Flags & YAMN_MSG_POPUP) && (msgq->Flags & YAMN_MSG_NEW))) { - - if (!Extracted) ExtractHeader(msgq->MailData->TranslatedHeader, msgq->MailData->CP, &UnicodeHeader); - Extracted = TRUE; - - if ((UnicodeHeader.From != nullptr) && (UnicodeHeader.FromNick != nullptr)) { - size_t size = mir_wstrlen(UnicodeHeader.From) + mir_wstrlen(UnicodeHeader.FromNick) + 4; - FromStr = new wchar_t[size]; - mir_snwprintf(FromStr, size, L"%s <%s>", UnicodeHeader.FromNick, UnicodeHeader.From); - FromStrNew = TRUE; - } - else if (UnicodeHeader.From != nullptr) - FromStr = UnicodeHeader.From; - else if (UnicodeHeader.FromNick != nullptr) - FromStr = UnicodeHeader.FromNick; - else if (UnicodeHeader.ReturnPath != nullptr) - FromStr = UnicodeHeader.ReturnPath; - - if (nullptr == FromStr) { - FromStr = L""; - FromStrNew = FALSE; - } - } - - if ((hListView != nullptr) && (msgq->Flags & YAMN_MSG_DISPLAY)) { - item.iSubItem = 0; - item.pszText = FromStr; - item.iItem = SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&item); - - item.iSubItem = 1; - item.pszText = (nullptr != UnicodeHeader.Subject ? UnicodeHeader.Subject : (wchar_t *)L""); - SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); - - item.iSubItem = 2; - mir_snwprintf(SizeStr, L"%d kB", msgq->MailData->Size / 1024); - item.pszText = SizeStr; - SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); - - item.iSubItem = 3; - item.pszText = L""; - - for (CMimeItem *heads = msgq->MailData->TranslatedHeader; heads != nullptr; heads = heads->Next) { - if (!_stricmp(heads->name, "Date")) { - MimeDateToLocalizedDateTime(heads->value, LocalDateStr, 128); - item.pszText = LocalDateStr; - break; - } - } - SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); - } - - if ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (msgq->Flags & YAMN_MSG_POPUP) && (msgq->Flags & YAMN_MSG_NEW)) { - mir_wstrncpy(NewMailPopup.lpwzContactName, FromStr, _countof(NewMailPopup.lpwzContactName)); - mir_wstrncpy(NewMailPopup.lpwzText, UnicodeHeader.Subject, _countof(NewMailPopup.lpwzText)); - - PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM)malloc(sizeof(YAMN_MAILSHOWPARAM)); - if (MailParam) { - MailParam->account = ActualAccount; - MailParam->mail = msgq; - MailParam->ThreadRunningEV = nullptr; - NewMailPopup.PluginData = MailParam; - PUAddPopupW(&NewMailPopup); - } - } - - if ((msgq->Flags & YAMN_MSG_UNSEEN) && (ActualAccount->NewMailN.Flags & YAMN_ACC_KBN)) - CallService(MS_KBDNOTIFY_EVENTSOPENED, 1, NULL); - - if (FromStrNew) - delete[] FromStr; - - if (Extracted) { - DeleteHeaderContent(&UnicodeHeader); - memset(&UnicodeHeader, 0, sizeof(UnicodeHeader)); - } - - if (!Loaded) { - SaveMailData(msgq); - UnloadMailData(msgq); //do not keep data for mail in memory - } - } - - return TRUE; -} - -void DoMailActions(HWND hDlg, CAccount *ActualAccount, struct CMailNumbers *MN, uint32_t nflags, uint32_t nnflags) -{ - NOTIFYICONDATA nid = {}; - nid.cbSize = sizeof(nid); - nid.hWnd = hDlg; - - if (MN->Real.EventNC + MN->Virtual.EventNC) - NotifyEventHooks(hNewMailHook, 0, 0); - - if ((nflags & YAMN_ACC_KBN) && (MN->Real.PopupRun + MN->Virtual.PopupRun)) - CallService(MS_KBDNOTIFY_STARTBLINK, (WPARAM)MN->Real.PopupNC + MN->Virtual.PopupNC, NULL); - - if ((nflags & YAMN_ACC_CONT) && (MN->Real.PopupRun + MN->Virtual.PopupRun)) { - wchar_t tszMsg[250]; - mir_snwprintf(tszMsg, TranslateT("%s : %d new mail message(s), %d total"), _A2T(ActualAccount->Name).get(), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); - - if (!(nflags & YAMN_ACC_CONTNOEVENT)) { - CLISTEVENT evt = {}; - evt.flags = CLEF_UNICODE; - evt.hContact = ActualAccount->hContact; - evt.hIcon = g_plugin.getIcon(IDI_NEWMAIL); - evt.hDbEvent = ActualAccount->hContact; - evt.lParam = ActualAccount->hContact; - evt.pszService = MS_YAMN_CLISTDBLCLICK; - evt.szTooltip.w = tszMsg; - g_clistApi.pfnAddEvent(&evt); - } - db_set_ws(ActualAccount->hContact, "CList", "StatusMsg", tszMsg); - - if (nflags & YAMN_ACC_CONTNICK) - g_plugin.setWString(ActualAccount->hContact, "Nick", tszMsg); - } - - if ((nflags & YAMN_ACC_POP) && - !(ActualAccount->Flags & YAMN_ACC_POPN) && - (MN->Real.PopupRun + MN->Virtual.PopupRun)) { - POPUPDATAW NewMailPopup; - - NewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; - NewMailPopup.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); - if (nflags & YAMN_ACC_POPC) { - NewMailPopup.colorBack = ActualAccount->NewMailN.PopupB; - NewMailPopup.colorText = ActualAccount->NewMailN.PopupT; - } - else { - NewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); - NewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - NewMailPopup.iSeconds = ActualAccount->NewMailN.PopupTime; - - NewMailPopup.PluginWindowProc = NewMailPopupProc; - NewMailPopup.PluginData = (void *)nullptr; //multiple popups - - mir_wstrncpy(NewMailPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(NewMailPopup.lpwzContactName)); - mir_snwprintf(NewMailPopup.lpwzText, TranslateT("%d new mail message(s), %d total"), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); - PUAddPopupW(&NewMailPopup); - } - - // destroy tray icon if no new mail - if ((MN->Real.SysTrayUC + MN->Virtual.SysTrayUC == 0) && (hDlg != nullptr)) - Shell_NotifyIcon(NIM_DELETE, &nid); - - // and remove the event - if ((nflags & YAMN_ACC_CONT) && (!(nflags & YAMN_ACC_CONTNOEVENT)) && (MN->Real.UnSeen + MN->Virtual.UnSeen == 0)) - g_clistApi.pfnRemoveEvent(ActualAccount->hContact, ActualAccount->hContact); - - if ((MN->Real.BrowserUC + MN->Virtual.BrowserUC == 0) && (hDlg != nullptr)) { - if (!IsWindowVisible(hDlg) && !(nflags & YAMN_ACC_MSG)) - PostMessage(hDlg, WM_DESTROY, 0, 0); //destroy window if no new mail and window is not visible - if (nnflags & YAMN_ACC_MSG) //if no new mail and msg should be executed - { - SetForegroundWindow(hDlg); - ShowWindow(hDlg, SW_SHOWNORMAL); - } - } - else - if (hDlg != nullptr) //else insert icon and set window if new mails - { - SendDlgItemMessageW(hDlg, IDC_LISTMAILS, LVM_SCROLL, 0, (LPARAM)0x7ffffff); - - if ((nflags & YAMN_ACC_ICO) && (MN->Real.SysTrayUC + MN->Virtual.SysTrayUC)) { - nid.hIcon = g_plugin.getIcon(IDI_NEWMAIL); - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - nid.uCallbackMessage = WM_YAMN_NOTIFYICON; - mir_snwprintf(nid.szTip, L"%S %s", ActualAccount->Name, TranslateT("- new mail message(s)")); - Shell_NotifyIcon(NIM_ADD, &nid); - SetTimer(hDlg, TIMER_FLASHING, 500, nullptr); - } - if (nflags & YAMN_ACC_MSG) //if no new mail and msg should be executed - ShowWindow(hDlg, SW_SHOWNORMAL); - } - - if (MN->Real.AppNC + MN->Virtual.AppNC != 0) { - if (nflags & YAMN_ACC_APP) { - PROCESS_INFORMATION pi; - STARTUPINFOW si; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - - if (ActualAccount->NewMailN.App != nullptr) { - wchar_t *Command; - if (ActualAccount->NewMailN.AppParam != nullptr) - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; - else - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; - - if (Command != nullptr) { - mir_wstrcpy(Command, L"\""); - mir_wstrcat(Command, ActualAccount->NewMailN.App); - mir_wstrcat(Command, L"\" "); - if (ActualAccount->NewMailN.AppParam != nullptr) - mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); - CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); - delete[] Command; - } - } - } - } - - if (MN->Real.SoundNC + MN->Virtual.SoundNC != 0) - if (nflags & YAMN_ACC_SND) - Skin_PlaySound(YAMN_NEWMAILSOUND); - - if ((nnflags & YAMN_ACC_POP) && (MN->Real.PopupRun + MN->Virtual.PopupRun == 0)) { - POPUPDATAW NoNewMailPopup = {}; - - NoNewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; - NoNewMailPopup.lchIcon = g_plugin.getIcon(IDI_LAUNCHAPP); - if (nflags & YAMN_ACC_POPC) { - NoNewMailPopup.colorBack = ActualAccount->NoNewMailN.PopupB; - NoNewMailPopup.colorText = ActualAccount->NoNewMailN.PopupT; - } - else { - NoNewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); - NoNewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - NoNewMailPopup.iSeconds = ActualAccount->NoNewMailN.PopupTime; - - NoNewMailPopup.PluginWindowProc = NoNewMailPopupProc; - NoNewMailPopup.PluginData = nullptr; //it's not new mail popup - - mir_wstrncpy(NoNewMailPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(NoNewMailPopup.lpwzContactName)); - if (MN->Real.PopupSL2NC + MN->Virtual.PopupSL2NC) - mir_snwprintf(NoNewMailPopup.lpwzText, TranslateT("No new mail message, %d spam(s)"), MN->Real.PopupSL2NC + MN->Virtual.PopupSL2NC); - else - mir_wstrncpy(NoNewMailPopup.lpwzText, TranslateT("No new mail message"), _countof(NoNewMailPopup.lpwzText)); - PUAddPopupW(&NoNewMailPopup); - } - - if ((nflags & YAMN_ACC_CONT) && (MN->Real.PopupRun + MN->Virtual.PopupRun == 0)) { - if (ActualAccount->hContact != NULL) { - if (MN->Real.PopupTC + MN->Virtual.PopupTC) { - char tmp[255]; - mir_snprintf(tmp, Translate("%d new mail message(s), %d total"), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); - db_set_s(ActualAccount->hContact, "CList", "StatusMsg", tmp); - } - else db_set_s(ActualAccount->hContact, "CList", "StatusMsg", Translate("No new mail message")); - - if (nflags & YAMN_ACC_CONTNICK) - g_plugin.setString(ActualAccount->hContact, "Nick", ActualAccount->Name); - } - } - return; -} - -LRESULT CALLBACK NewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - INT_PTR PluginParam = 0; - switch (msg) { - case WM_COMMAND: - // if clicked and it's new mail popup window - if ((HIWORD(wParam) == STN_CLICKED) && (-1 != (PluginParam = (INT_PTR)PUGetPluginData(hWnd)))) { - MCONTACT hContact = 0; - CAccount *Account; - if (PluginParam) { - PYAMN_MAILSHOWPARAM MailParam = new YAMN_MAILSHOWPARAM; - memcpy(MailParam, (PINT_PTR)PluginParam, sizeof(YAMN_MAILSHOWPARAM)); - hContact = MailParam->account->hContact; - Account = MailParam->account; - mir_forkthread(ShowEmailThread, MailParam); - } - else { - DBVARIANT dbv; - - hContact = PUGetContact(hWnd); - - if (!g_plugin.getString(hContact, "Id", &dbv)) { - Account = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - db_free(&dbv); - } - else Account = (CAccount *)hContact; //???? - - if (WAIT_OBJECT_0 == WaitToReadFcn(Account->AccountAccessSO)) { - switch (msg) { - case WM_COMMAND: - { - YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, Account, - (Account->NewMailN.Flags & ~YAMN_ACC_POP) | YAMN_ACC_MSGP | YAMN_ACC_MSG, - (Account->NoNewMailN.Flags & ~YAMN_ACC_POP) | YAMN_ACC_MSGP | YAMN_ACC_MSG}; - - RunMailBrowserSvc((WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); - } - break; - } - ReadDoneFcn(Account->AccountAccessSO); - } - } - if ((Account->NewMailN.Flags & YAMN_ACC_CONT) && !(Account->NewMailN.Flags & YAMN_ACC_CONTNOEVENT)) - g_clistApi.pfnRemoveEvent(hContact, hContact); - } - __fallthrough; - - case WM_CONTEXTMENU: - PUDeletePopup(hWnd); - break; - case UM_FREEPLUGINDATA: - { - PYAMN_MAILSHOWPARAM mpd = (PYAMN_MAILSHOWPARAM)PUGetPluginData(hWnd); - if ((mpd) && (INT_PTR)mpd != -1)free(mpd); - return FALSE; - } - case UM_INITPOPUP: - //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. - WindowList_Add(YAMNVar.MessageWnds, hWnd); - break; - case UM_DESTROYPOPUP: - WindowList_Remove(YAMNVar.MessageWnds, hWnd); - break; - case WM_YAMN_STOPACCOUNT: - { - CAccount *ActualAccount; - DBVARIANT dbv; - - MCONTACT hContact = PUGetContact(hWnd); - - if (!g_plugin.getString(hContact, "Id", &dbv)) { - ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - db_free(&dbv); - } - else - ActualAccount = (CAccount *)hContact; - - if ((CAccount *)wParam != ActualAccount) - break; - DestroyWindow(hWnd); - return 0; - } - case WM_NOTIFY: - default: - break; - } - return DefWindowProc(hWnd, msg, wParam, lParam); -} - -LRESULT CALLBACK NoNewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_COMMAND: - if ((HIWORD(wParam) == STN_CLICKED) && (msg == WM_COMMAND)) { - CAccount *ActualAccount; - DBVARIANT dbv; - - MCONTACT hContact = PUGetContact(hWnd); - - if (!g_plugin.getString(hContact, "Id", &dbv)) { - ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - db_free(&dbv); - } - else - ActualAccount = (CAccount *)hContact; - - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - switch (msg) { - case WM_COMMAND: - { - YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualAccount->NewMailN.Flags, ActualAccount->NoNewMailN.Flags, nullptr}; - - Param.nnflags = Param.nnflags | YAMN_ACC_MSG; //show mails in account even no new mail in account - Param.nnflags = Param.nnflags & ~YAMN_ACC_POP; - - Param.nflags = Param.nflags | YAMN_ACC_MSG; //show mails in account even no new mail in account - Param.nflags = Param.nflags & ~YAMN_ACC_POP; - - RunMailBrowserSvc((WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); - } - break; - } - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - PUDeletePopup(hWnd); - } - break; - - case WM_CONTEXTMENU: - PUDeletePopup(hWnd); - break; - - case UM_FREEPLUGINDATA: - //Here we'd free our own data, if we had it. - return FALSE; - case UM_INITPOPUP: - //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. - WindowList_Add(YAMNVar.MessageWnds, hWnd); - break; - case UM_DESTROYPOPUP: - WindowList_Remove(YAMNVar.MessageWnds, hWnd); - break; - case WM_YAMN_STOPACCOUNT: - { - CAccount *ActualAccount; - DBVARIANT dbv; - - MCONTACT hContact = PUGetContact(hWnd); - - if (!g_plugin.getString(hContact, "Id", &dbv)) { - ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - db_free(&dbv); - } - else - ActualAccount = (CAccount *)hContact; - - if ((CAccount *)wParam != ActualAccount) - break; - - DestroyWindow(hWnd); - return 0; - } - } - return DefWindowProc(hWnd, msg, wParam, lParam); -} - -#ifdef __GNUC__ -//number of 100 ns periods between FILETIME 0 (1601/01/01 00:00:00.0000000) and TIMESTAMP 0 (1970/01/01 00:00:00) -#define NUM100NANOSEC 116444736000000000ULL -//The biggest time Get[Date|Time]Format can handle (Fri, 31 Dec 30827 23:59:59.9999999) -#define MAXFILETIME 0x7FFF35F4F06C7FFFULL -#else -#define NUM100NANOSEC 116444736000000000 -#define MAXFILETIME 0x7FFF35F4F06C7FFF -#endif - -ULONGLONG MimeDateToFileTime(char *datein) -{ - char *day = nullptr, *month = nullptr, *year = nullptr, *time = nullptr, *shift = nullptr; - SYSTEMTIME st; - ULONGLONG res = 0; - int wShiftSeconds = TimeZone_ToLocal(0); - GetLocalTime(&st); - //datein = "Xxx, 1 Jan 2060 5:29:1 +0530 XXX"; - //datein = "Xxx, 1 Jan 2060 05:29:10 "; - //datein = " ManySpaces 1.5 Jan 2060 05::"; - //datein = "Xxx, 35 February 20 :29:10 "; - //datein = "01.12.2007 (22:38:17)"; // - if (datein) { - char tmp[64]; - while (datein[0] == ' ') datein++; // eat leading spaces - strncpy(tmp, datein, 63); tmp[63] = 0; - if (atoi(tmp)) { // Parseable integer on DayOfWeek field? Buggy mime date. - day = tmp; - } - else { - int i = 0; - while (tmp[i] == ' ')i++; if (day = strchr(&tmp[i], ' ')) { day[0] = 0; day++; } - } - if (day) { while (day[0] == ' ') day++; if (month = strchr(day, ' ')) { month[0] = 0; month++; } } - if (month) { while (month[0] == ' ')month++; if (year = strchr(month, ' ')) { year[0] = 0; year++; } } - if (year) { while (year[0] == ' ') year++; if (time = strchr(year, ' ')) { time[0] = 0; time++; } } - if (time) { while (time[0] == ' ') time++; if (shift = strchr(time, ' ')) { shift[0] = 0; shift++; shift[5] = 0; } } - - if (year) { - st.wYear = atoi(year); - if (mir_strlen(year) < 4) if (st.wYear < 70)st.wYear += 2000; else st.wYear += 1900; - }; - if (month) for (int i = 0; i < 12; i++) if (strncmp(month, s_MonthNames[i], 3) == 0) { st.wMonth = i + 1; break; } - if (day) st.wDay = atoi(day); - if (time) { - char *h, *m, *s; - h = time; - if (m = strchr(h, ':')) { - m[0] = 0; m++; - if (s = strchr(m, ':')) { s[0] = 0; s++; } - } - else s = nullptr; - st.wHour = atoi(h); - st.wMinute = m ? atoi(m) : 0; - st.wSecond = s ? atoi(s) : 0; - } - else { st.wHour = st.wMinute = st.wSecond = 0; } - - if (shift) { - if (mir_strlen(shift) < 4) { - //has only hour - wShiftSeconds = (atoi(shift)) * 3600; - } - else { - char *smin = shift + mir_strlen(shift) - 2; - int ismin = atoi(smin); - smin[0] = 0; - int ishour = atoi(shift); - wShiftSeconds = (ishour * 60 + (ishour < 0 ? -1 : 1) * ismin) * 60; - } - } - } // if (datein) - FILETIME ft; - if (SystemTimeToFileTime(&st, &ft)) { - res = ((ULONGLONG)ft.dwHighDateTime << 32) | ((ULONGLONG)ft.dwLowDateTime); - LONGLONG w100nano = Int32x32To64((uint32_t)wShiftSeconds, 10000000); - res -= w100nano; - } - else { - res = 0; - } - return res; -} - -void FileTimeToLocalizedDateTime(LONGLONG filetime, wchar_t *dateout, int lendateout) -{ - int localeID = Langpack_GetDefaultLocale(); - //int localeID = MAKELCID(LANG_URDU, SORT_DEFAULT); - if (localeID == CALLSERVICE_NOTFOUND) localeID = LOCALE_USER_DEFAULT; - if (filetime > MAXFILETIME) filetime = MAXFILETIME; - else if (filetime <= 0) { - wcsncpy(dateout, TranslateT("Invalid"), lendateout); - return; - } - SYSTEMTIME st; - uint16_t wTodayYear = 0, wTodayMonth = 0, wTodayDay = 0; - FILETIME ft; - BOOL willShowDate = !(optDateTime & SHOWDATENOTODAY); - if (!willShowDate) { - GetLocalTime(&st); - wTodayYear = st.wYear; - wTodayMonth = st.wMonth; - wTodayDay = st.wDay; - } - ft.dwLowDateTime = (uint32_t)filetime; - ft.dwHighDateTime = (uint32_t)(filetime >> 32); - FILETIME localft; - if (!FileTimeToLocalFileTime(&ft, &localft)) { - // this should never happen - wcsncpy(dateout, L"Incorrect FileTime", lendateout); - } - else { - if (!FileTimeToSystemTime(&localft, &st)) { - // this should never happen - wcsncpy(dateout, L"Incorrect LocalFileTime", lendateout); - } - else { - dateout[lendateout - 1] = 0; - int templen = 0; - if (!willShowDate) willShowDate = (wTodayYear != st.wYear) || (wTodayMonth != st.wMonth) || (wTodayDay != st.wDay); - if (willShowDate) { - templen = GetDateFormatW(localeID, (optDateTime & SHOWDATELONG) ? DATE_LONGDATE : DATE_SHORTDATE, &st, nullptr, dateout, lendateout - 2); - dateout[templen - 1] = ' '; - } - if (templen < (lendateout - 1)) { - GetTimeFormatW(localeID, (optDateTime & SHOWDATENOSECONDS) ? TIME_NOSECONDS : 0, &st, nullptr, &dateout[templen], lendateout - templen - 1); - } - } - } -} - -void MimeDateToLocalizedDateTime(char *datein, wchar_t *dateout, int lendateout) -{ - ULONGLONG ft = MimeDateToFileTime(datein); - FileTimeToLocalizedDateTime(ft, dateout, lendateout); -} - -int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) -{ - if (lParam1 == NULL || lParam2 == NULL) - return 0; - - int nResult = 0; - char *str1; - char *str2; - HYAMNMAIL email1 = (HYAMNMAIL)lParam1; - HYAMNMAIL email2 = (HYAMNMAIL)lParam2; - struct CShortHeader Header1; - struct CShortHeader Header2; - memset(&Header1, 0, sizeof(Header1)); - memset(&Header2, 0, sizeof(Header2)); - - try { - ExtractShortHeader(email1->MailData->TranslatedHeader, &Header1); - ExtractShortHeader(email2->MailData->TranslatedHeader, &Header2); - - switch ((int)lParamSort) { - case 0: //From - if (Header1.FromNick == nullptr) - str1 = Header1.From; - else str1 = Header1.FromNick; - - if (Header2.FromNick == nullptr) - str2 = Header2.From; - else str2 = Header2.FromNick; - - nResult = mir_strcmp(str1, str2); - - if (bFrom) nResult = -nResult; - break; - case 1: //Subject - if (Header1.Subject == nullptr) - str1 = " "; - else str1 = Header1.Subject; - - if (Header2.Subject == nullptr) - str2 = " "; - else str2 = Header2.Subject; - - nResult = mir_strcmp(str1, str2); - - if (bSub) nResult = -nResult; - break; - case 2: //Size - if (email1->MailData->Size == email2->MailData->Size) nResult = 0; - if (email1->MailData->Size > email2->MailData->Size) nResult = 1; - if (email1->MailData->Size < email2->MailData->Size) nResult = -1; - - if (bSize) nResult = -nResult; - break; - - case 3: //Date - { - ULONGLONG ts1 = 0, ts2 = 0; - ts1 = MimeDateToFileTime(Header1.Date); - ts2 = MimeDateToFileTime(Header2.Date); - if (ts1 > ts2) nResult = 1; - else if (ts1 < ts2) nResult = -1; - else nResult = 0; - } - if (bDate) nResult = -nResult; - break; - - default: - if (Header1.Subject == nullptr) str1 = " "; - else str1 = Header1.Subject; - - if (Header2.Subject == nullptr) str2 = " "; - else str2 = Header2.Subject; - - nResult = mir_strcmp(str1, str2); - break; - } - //MessageBox(NULL,str1,str2,0); - } - catch (...) { - } - - //free mem - DeleteShortHeaderContent(&Header1); - DeleteShortHeaderContent(&Header2); - return nResult; - -} - -HCURSOR hCurSplitNS, hCurSplitWE; -#define DM_SPLITTERMOVED (WM_USER+15) - -static LRESULT CALLBACK SplitterSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_NCHITTEST: - return HTCLIENT; - - case WM_SETCURSOR: - SetCursor(hCurSplitNS); - return TRUE; - - case WM_LBUTTONDOWN: - SetCapture(hwnd); - return 0; - - case WM_MOUSEMOVE: - if (GetCapture() == hwnd) { - RECT rc; - GetClientRect(hwnd, &rc); - SendMessage(GetParent(hwnd), DM_SPLITTERMOVED, (short)HIWORD(GetMessagePos()) + rc.bottom / 2, (LPARAM)hwnd); - } - return 0; - - case WM_LBUTTONUP: - ReleaseCapture(); - return 0; - } - return mir_callNextSubclass(hwnd, SplitterSubclassProc, msg, wParam, lParam); -} - -void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); -int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); - -INT_PTR CALLBACK DlgProcYAMNShowMessage(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - { - PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM)lParam; - wchar_t *iHeaderW = nullptr; - wchar_t *iValueW = nullptr; - int StrLen; - HWND hListView = GetDlgItem(hDlg, IDC_LISTHEADERS); - mir_subclassWindow(GetDlgItem(hDlg, IDC_SPLITTER), SplitterSubclassProc); - SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)MailParam); - Window_SetIcon_IcoLib(hDlg, g_plugin.getIconHandle(IDI_NEWMAIL)); - - ListView_SetUnicodeFormat(hListView, TRUE); - ListView_SetExtendedListViewStyle(hListView, LVS_EX_FULLROWSELECT); - - StrLen = MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Header"), -1, nullptr, 0); - iHeaderW = new wchar_t[StrLen + 1]; - MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Header"), -1, iHeaderW, StrLen); - - StrLen = MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Value"), -1, nullptr, 0); - iValueW = new wchar_t[StrLen + 1]; - MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Value"), -1, iValueW, StrLen); - - LVCOLUMN lvc0 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, 130, iHeaderW, 0, 0}; - LVCOLUMN lvc1 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, 400, iValueW, 0, 0}; - SendMessage(hListView, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc0); - SendMessage(hListView, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc1); - if (nullptr != iHeaderW) - delete[] iHeaderW; - if (nullptr != iValueW) - delete[] iValueW; - - SendMessage(hDlg, WM_YAMN_CHANGECONTENT, 0, (LPARAM)MailParam); - MoveWindow(hDlg, HeadPosX, HeadPosY, HeadSizeX, HeadSizeY, 0); - ShowWindow(hDlg, SW_SHOWNORMAL); - } - break; - - case WM_YAMN_CHANGECONTENT: - { - PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM) - (lParam ? lParam : GetWindowLongPtr(hDlg, DWLP_USER)); - HWND hListView = GetDlgItem(hDlg, IDC_LISTHEADERS); - HWND hEdit = GetDlgItem(hDlg, IDC_EDITBODY); - //do not redraw - SendMessage(hListView, WM_SETREDRAW, 0, 0); - ListView_DeleteAllItems(hListView); - struct CMimeItem *Header; - LVITEMW item; - item.mask = LVIF_TEXT | LVIF_PARAM; - wchar_t *From = nullptr, *Subj = nullptr; - char *contentType = nullptr, *transEncoding = nullptr, *body = nullptr; //should not be delete[]-ed - for (Header = MailParam->mail->MailData->TranslatedHeader; Header != nullptr; Header = Header->Next) { - wchar_t *str1 = nullptr; - wchar_t *str2 = nullptr; - wchar_t str_nul[2] = {0}; - if (!body) if (!_stricmp(Header->name, "Body")) { body = Header->value; continue; } - if (!contentType) if (!_stricmp(Header->name, "Content-Type")) contentType = Header->value; - if (!transEncoding) if (!_stricmp(Header->name, "Content-Transfer-Encoding")) transEncoding = Header->value; - //ConvertCodedStringToUnicode(Header->name,&str1,MailParam->mail->MailData->CP,1); - { - int streamsize = MultiByteToWideChar(20127, 0, Header->name, -1, nullptr, 0); - str1 = (wchar_t *)malloc(sizeof(wchar_t) * (streamsize + 1)); - MultiByteToWideChar(20127, 0, Header->name, -1, str1, streamsize);//US-ASCII - } - ConvertCodedStringToUnicode(Header->value, &str2, MailParam->mail->MailData->CP, 1); - if (!str2) { str2 = (wchar_t *)str_nul; }// the header value may be NULL - if (!From) if (!_stricmp(Header->name, "From")) { - From = new wchar_t[mir_wstrlen(str2) + 1]; - mir_wstrcpy(From, str2); - } - if (!Subj) if (!_stricmp(Header->name, "Subject")) { - Subj = new wchar_t[mir_wstrlen(str2) + 1]; - mir_wstrcpy(Subj, str2); - } - //if (!hasBody) if (!mir_strcmp(Header->name,"Body")) hasBody = true; - int count = 0; wchar_t **split = nullptr; - int ofs = 0; - while (str2[ofs]) { - if ((str2[ofs] == 0x266A) || (str2[ofs] == 0x25D9) || (str2[ofs] == 0x25CB) || - (str2[ofs] == 0x09) || (str2[ofs] == 0x0A) || (str2[ofs] == 0x0D))count++; - ofs++; - } - split = new wchar_t *[count + 1]; - count = 0; ofs = 0; - split[0] = str2; - while (str2[ofs]) { - if ((str2[ofs] == 0x266A) || (str2[ofs] == 0x25D9) || (str2[ofs] == 0x25CB) || - (str2[ofs] == 0x09) || (str2[ofs] == 0x0A) || (str2[ofs] == 0x0D)) { - if (str2[ofs - 1]) { - count++; - } - split[count] = (wchar_t *)(str2 + ofs + 1); - str2[ofs] = 0; - } - ofs++; - }; - - if (!_stricmp(Header->name, "From") || !_stricmp(Header->name, "To") || !_stricmp(Header->name, "Date") || !_stricmp(Header->name, "Subject")) - item.iItem = 0; - else - item.iItem = 999; - for (int i = 0; i <= count; i++) { - item.iSubItem = 0; - if (i == 0) - item.pszText = str1; - else { - item.iItem++; - item.pszText = nullptr; - } - item.iItem = SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&item); - item.iSubItem = 1; - item.pszText = str2 ? split[i] : nullptr; - SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); - } - delete[] split; - - if (str1) - free(str1); - if (str2 != (wchar_t *)str_nul) - free(str2); - } - if (body) { - wchar_t *bodyDecoded = nullptr; - char *localBody = nullptr; - if (contentType) { - if (!_strnicmp(contentType, "text", 4)) { - if (transEncoding) { - if (!_stricmp(transEncoding, "base64")) { - int size = (int)mir_strlen(body) * 3 / 4 + 5; - localBody = new char[size + 1]; - DecodeBase64(body, localBody, size); - } - else if (!_stricmp(transEncoding, "quoted-printable")) { - int size = (int)mir_strlen(body) + 2; - localBody = new char[size + 1]; - DecodeQuotedPrintable(body, localBody, size, FALSE); - } - } - } - else if (!_strnicmp(contentType, "multipart/", 10)) { - char *bondary = nullptr; - if (nullptr != (bondary = ExtractFromContentType(contentType, "boundary="))) { - bodyDecoded = ParseMultipartBody(body, bondary); - delete[] bondary; - } - } - } - if (!bodyDecoded)ConvertStringToUnicode(localBody ? localBody : body, MailParam->mail->MailData->CP, &bodyDecoded); - SetWindowTextW(hEdit, bodyDecoded); - delete[] bodyDecoded; - if (localBody) delete[] localBody; - SetFocus(hEdit); - } - if (!(MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED)) { - MailParam->mail->Flags |= YAMN_MSG_BODYREQUESTED; - CallService(MS_YAMN_ACCOUNTCHECK, (WPARAM)MailParam->account, 0); - } - else { - if (MailParam->mail->Flags & YAMN_MSG_UNSEEN) { - MailParam->mail->Flags &= ~YAMN_MSG_UNSEEN; //mark the message as seen - HWND hMailBrowser = WindowList_Find(YAMNVar.NewMailAccountWnd, (UINT_PTR)MailParam->account); - if (hMailBrowser) { - struct CChangeContent Params = {MailParam->account->NewMailN.Flags | YAMN_ACC_MSGP, MailParam->account->NoNewMailN.Flags | YAMN_ACC_MSGP}; - SendMessage(hMailBrowser, WM_YAMN_CHANGECONTENT, (WPARAM)MailParam->account, (LPARAM)&Params); - } - else UpdateMails(nullptr, MailParam->account, MailParam->account->NewMailN.Flags, MailParam->account->NoNewMailN.Flags); - } - } - ShowWindow(GetDlgItem(hDlg, IDC_SPLITTER), (MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED) ? SW_SHOW : SW_HIDE); - ShowWindow(hEdit, (MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED) ? SW_SHOW : SW_HIDE); - wchar_t *title = nullptr; - size_t size = (From ? mir_wstrlen(From) : 0) + (Subj ? mir_wstrlen(Subj) : 0) + 4; - title = new wchar_t[size]; - if (From && Subj) - mir_snwprintf(title, size, L"%s (%s)", Subj, From); - else if (From) - wcsncpy_s(title, size, From, _TRUNCATE); - else if (Subj) - wcsncpy_s(title, size, Subj, _TRUNCATE); - else - wcsncpy_s(title, size, L"none", _TRUNCATE); - if (Subj) delete[] Subj; - if (From) delete[] From; - SetWindowTextW(hDlg, title); - delete[] title; - // turn on redrawing - SendMessage(hListView, WM_SETREDRAW, 1, 0); - SendMessage(hDlg, WM_SIZE, 0, HeadSizeY << 16 | HeadSizeX); - } break; - - case WM_YAMN_STOPACCOUNT: - { - PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM) - (lParam ? lParam : GetWindowLongPtr(hDlg, DWLP_USER)); - - if (nullptr == MailParam) - break; - if ((CAccount *)wParam != MailParam->account) - break; - - DestroyWindow(hDlg); - } - return 1; - - case WM_CTLCOLORSTATIC: - // here should be check if this is our edittext control. - // but we have only one static control (for now); - SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); - SetTextColor((HDC)wParam, GetSysColor(COLOR_WINDOWTEXT)); - return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); - - case WM_DESTROY: - Window_FreeIcon_IcoLib(hDlg); - { - RECT coord; - if (GetWindowRect(hDlg, &coord)) { - HeadPosX = coord.left; - HeadSizeX = coord.right - coord.left; - HeadPosY = coord.top; - HeadSizeY = coord.bottom - coord.top; - } - - PostQuitMessage(1); - } - break; - - case WM_SYSCOMMAND: - switch (wParam) { - case SC_CLOSE: - DestroyWindow(hDlg); - break; - } - break; - - case WM_MOVE: - HeadPosX = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; - HeadPosY = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; - return 0; - - case DM_SPLITTERMOVED: - if ((HWND)lParam == GetDlgItem(hDlg, IDC_SPLITTER)) { - POINT pt; - pt.x = 0; - pt.y = wParam; - ScreenToClient(hDlg, &pt); - HeadSplitPos = (pt.y * 1000) / HeadSizeY;//+rc.bottom-rc.top; - if (HeadSplitPos >= 1000) HeadSplitPos = 999; - else if (HeadSplitPos <= 0) HeadSplitPos = 1; - else SendMessage(hDlg, WM_SIZE, 0, HeadSizeY << 16 | HeadSizeX); - } - return 0; - - case WM_SIZE: - if (wParam == SIZE_RESTORED) { - HWND hList = GetDlgItem(hDlg, IDC_LISTHEADERS); - HWND hEdit = GetDlgItem(hDlg, IDC_EDITBODY); - BOOL isBodyShown = ((PYAMN_MAILSHOWPARAM)(GetWindowLongPtr(hDlg, DWLP_USER)))->mail->Flags & YAMN_MSG_BODYRECEIVED; - HeadSizeX = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; - HeadSizeY = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; - int localSplitPos = (HeadSplitPos * HeadSizeY) / 1000; - int localSizeX; - RECT coord; - MoveWindow(GetDlgItem(hDlg, IDC_SPLITTER), 5, localSplitPos, HeadSizeX - 10, 2, TRUE); - MoveWindow(hEdit, 5, localSplitPos + 6, HeadSizeX - 10, HeadSizeY - localSplitPos - 11, TRUE); //where to put text window while resizing - MoveWindow(hList, 5, 5, HeadSizeX - 10, (isBodyShown ? localSplitPos : HeadSizeY) - 10, TRUE); //where to put headers list window while resizing - //if (changeX) { - if (GetClientRect(hList, &coord)) { - localSizeX = coord.right - coord.left; - } - else localSizeX = HeadSizeX; - LONG iNameWidth = ListView_GetColumnWidth(hList, 0); - ListView_SetColumnWidth(hList, 1, (localSizeX <= iNameWidth) ? 0 : (localSizeX - iNameWidth)); - //} - } - return 0; - - case WM_CONTEXTMENU: - if (GetWindowLongPtr((HWND)wParam, GWLP_ID) == IDC_LISTHEADERS) { - //MessageBox(0,"LISTHEADERS","Debug",0); - HWND hList = GetDlgItem(hDlg, IDC_LISTHEADERS); - POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - if (pt.x == -1) pt.x = 0; - if (pt.y == -1) pt.y = 0; - if (int numRows = ListView_GetItemCount(hList)) { - HMENU hMenu = CreatePopupMenu(); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy Selected")); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy All")); - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); - int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hDlg, nullptr); - DestroyMenu(hMenu); - if (nReturnCmd > 0) { - int courRow = 0; - size_t sizeNeeded = 0; - wchar_t headname[64] = {0}, headvalue[256] = {0}; - for (courRow = 0; courRow < numRows; courRow++) { - if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; - ListView_GetItemText(hList, courRow, 0, headname, _countof(headname)); - ListView_GetItemText(hList, courRow, 1, headvalue, _countof(headvalue)); - size_t headnamelen = mir_wstrlen(headname); - if (headnamelen) sizeNeeded += 1 + headnamelen; - sizeNeeded += 3 + mir_wstrlen(headvalue); - } - if (sizeNeeded && OpenClipboard(hDlg)) { - EmptyClipboard(); - HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE, (sizeNeeded + 1) * sizeof(wchar_t)); - wchar_t *buff = (wchar_t *)GlobalLock(hData); - int courPos = 0; - for (courRow = 0; courRow < numRows; courRow++) { - if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; - ListView_GetItemText(hList, courRow, 0, headname, _countof(headname)); - ListView_GetItemText(hList, courRow, 1, headvalue, _countof(headvalue)); - if (mir_wstrlen(headname)) courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"%s:\t%s\r\n", headname, headvalue); - else courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"\t%s\r\n", headvalue); - } - GlobalUnlock(hData); - - SetClipboardData(CF_UNICODETEXT, hData); - - CloseClipboard(); - } - } - } - } - break; // just in case - } - return 0; -} - -void __cdecl ShowEmailThread(void *Param) -{ - struct MailShowMsgWinParam MyParam = *(struct MailShowMsgWinParam *)Param; - - SCIncFcn(MyParam.account->UsingThreads); - - if (MyParam.mail->MsgWindow) { - //if (!BringWindowToTop(MyParam.mail->MsgWindow)) { - if (!SetForegroundWindow(MyParam.mail->MsgWindow)) { - SendMessage(MyParam.mail->MsgWindow, WM_DESTROY, 0, 0); - MyParam.mail->MsgWindow = nullptr; - goto CREADTEVIEWMESSAGEWINDOW; - } - - if (IsIconic(MyParam.mail->MsgWindow)) - OpenIcon(MyParam.mail->MsgWindow); - } - else { -CREADTEVIEWMESSAGEWINDOW: - MyParam.mail->MsgWindow = CreateDialogParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_DLGSHOWMESSAGE), nullptr, DlgProcYAMNShowMessage, (LPARAM)&MyParam); - WindowList_Add(YAMNVar.MessageWnds, MyParam.mail->MsgWindow); - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) { - if (MyParam.mail->MsgWindow == nullptr || !IsDialogMessage(MyParam.mail->MsgWindow, &msg)) { /* Wine fix. */ - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - WindowList_Remove(YAMNVar.MessageWnds, MyParam.mail->MsgWindow); - MyParam.mail->MsgWindow = nullptr; - } - - SCDecFcn(MyParam.account->UsingThreads); - delete (struct MailShowMsgWinParam *)Param; -} - -INT_PTR CALLBACK DlgProcYAMNMailBrowser(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - CAccount *ActualAccount; - int Items; - - switch (msg) { - case WM_INITDIALOG: - { - struct MailBrowserWinParam *MyParam = (struct MailBrowserWinParam *)lParam; - - ListView_SetUnicodeFormat(GetDlgItem(hDlg, IDC_LISTMAILS), TRUE); - ListView_SetExtendedListViewStyle(GetDlgItem(hDlg, IDC_LISTMAILS), LVS_EX_FULLROWSELECT); - - ActualAccount = MyParam->account; - struct CMailWinUserInfo *mwui = new struct CMailWinUserInfo; - mwui->Account = ActualAccount; - mwui->TrayIconState = 0; - mwui->UpdateMailsMessagesAccess = FALSE; - mwui->Seen = FALSE; - mwui->RunFirstTime = TRUE; - - SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)mwui); - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { - DestroyWindow(hDlg); - return FALSE; - } - - SetDlgItemText(hDlg, IDC_BTNAPP, TranslateT("Run application")); - SetDlgItemText(hDlg, IDC_BTNDEL, TranslateT("Delete selected")); - SetDlgItemText(hDlg, IDC_BTNCHECKALL, TranslateT("Select All")); - SetDlgItemText(hDlg, IDC_BTNOK, TranslateT("OK")); - - LVCOLUMN lvc0 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, FromWidth, TranslateT("From"), 0, 0}; - LVCOLUMN lvc1 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SubjectWidth, TranslateT("Subject"), 0, 0}; - LVCOLUMN lvc2 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SizeWidth, TranslateT("Size"), 0, 0}; - LVCOLUMN lvc3 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SizeDate, TranslateT("Date"), 0, 0}; - SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc0); - SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc1); - SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, (WPARAM)2, (LPARAM)&lvc2); - SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, (WPARAM)3, (LPARAM)&lvc3); - - if ((ActualAccount->NewMailN.App != nullptr) && (mir_wstrlen(ActualAccount->NewMailN.App))) - EnableWindow(GetDlgItem(hDlg, IDC_BTNAPP), TRUE); - else - EnableWindow(GetDlgItem(hDlg, IDC_BTNAPP), FALSE); - - ReadDoneFcn(ActualAccount->AccountAccessSO); - - WindowList_Add(YAMNVar.MessageWnds, hDlg); - WindowList_Add(YAMNVar.NewMailAccountWnd, hDlg, (UINT_PTR)ActualAccount); - - { - wchar_t accstatus[512]; - GetStatusFcn(ActualAccount, accstatus); - SetDlgItemText(hDlg, IDC_STSTATUS, accstatus); - } - SetTimer(hDlg, TIMER_FLASHING, 500, nullptr); - - if (ActualAccount->hContact != NULL) - g_clistApi.pfnRemoveEvent(ActualAccount->hContact, (LPARAM)"yamn new mail message"); - - mir_subclassWindow(GetDlgItem(hDlg, IDC_LISTMAILS), ListViewSubclassProc); - } - break; - - case WM_DESTROY: - { - RECT coord; - LVCOLUMN ColInfo; - HYAMNMAIL Parser; - - Window_FreeIcon_IcoLib(hDlg); - - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - ColInfo.mask = LVCF_WIDTH; - if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 0, &ColInfo)) - FromWidth = ColInfo.cx; - if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 1, &ColInfo)) - SubjectWidth = ColInfo.cx; - if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 2, &ColInfo)) - SizeWidth = ColInfo.cx; - if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 3, &ColInfo)) - SizeDate = ColInfo.cx; - - if (!YAMNVar.Shutdown && GetWindowRect(hDlg, &coord)) //the YAMNVar.Shutdown testing is because MMessagesAccessSO)) - break; - - //delete mails from queue, which are deleted from server (spam level 3 mails e.g.) - for (Parser = (HYAMNMAIL)ActualAccount->Mails; Parser != nullptr; Parser = Parser->Next) { - if ((Parser->Flags & YAMN_MSG_DELETED) && YAMN_MSG_SPAML(Parser->Flags, YAMN_MSG_SPAML3) && mwui->Seen) //if spaml3 was already deleted and user knows about it - { - DeleteMessageFromQueueFcn((HYAMNMAIL *)&ActualAccount->Mails, Parser, 1); - CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)ActualAccount->Plugin, (LPARAM)Parser); - } - } - - //mark mails as read (remove "new" and "unseen" flags) - if (mwui->Seen) - SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY, 0, YAMN_MSG_NEW | YAMN_MSG_UNSEEN, 0); - - WriteDoneFcn(ActualAccount->MessagesAccessSO); - - NOTIFYICONDATA nid; - memset(&nid, 0, sizeof(NOTIFYICONDATA)); - - delete mwui; - SetWindowLongPtr(hDlg, DWLP_USER, NULL); - - nid.cbSize = sizeof(NOTIFYICONDATA); - nid.hWnd = hDlg; - nid.uID = 0; - Shell_NotifyIcon(NIM_DELETE, &nid); - PostQuitMessage(0); - } - break; - - case WM_SHOWWINDOW: - { - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - - if (mwui == nullptr) - return 0; - mwui->Seen = TRUE; - } - - case WM_YAMN_CHANGESTATUS: - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - - if ((CAccount *)wParam != ActualAccount) - break; - - wchar_t accstatus[512]; - GetStatusFcn(ActualAccount, accstatus); - SetDlgItemText(hDlg, IDC_STSTATUS, accstatus); - return 1; - - case WM_YAMN_CHANGECONTENT: - { - struct CUpdateMails UpdateParams; - BOOL ThisThreadWindow = (GetCurrentThreadId() == GetWindowThreadProcessId(hDlg, nullptr)); - - if (nullptr == (UpdateParams.Copied = CreateEvent(nullptr, FALSE, FALSE, nullptr))) { - DestroyWindow(hDlg); - return 0; - } - UpdateParams.Flags = (struct CChangeContent *)lParam; - UpdateParams.Waiting = !ThisThreadWindow; - - if (ThisThreadWindow) { - if (!UpdateMails(hDlg, (CAccount *)wParam, UpdateParams.Flags->nflags, UpdateParams.Flags->nnflags)) - DestroyWindow(hDlg); - } - else if (PostMessage(hDlg, WM_YAMN_UPDATEMAILS, wParam, (LPARAM)&UpdateParams)) //this ensures UpdateMails will execute the thread who created the browser window - { - if (!ThisThreadWindow) - WaitForSingleObject(UpdateParams.Copied, INFINITE); - } - - CloseHandle(UpdateParams.Copied); - } - return 1; - case WM_YAMN_UPDATEMAILS: - { - struct CUpdateMails *um = (struct CUpdateMails *)lParam; - uint32_t nflags, nnflags; - - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - return 0; - if ((CAccount *)wParam != ActualAccount) - return 0; - - nflags = um->Flags->nflags; - nnflags = um->Flags->nnflags; - - if (um->Waiting) - SetEvent(um->Copied); - - if (!UpdateMails(hDlg, ActualAccount, nflags, nnflags)) - DestroyWindow(hDlg); - } - return 1; - case WM_YAMN_STOPACCOUNT: - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - if ((CAccount *)wParam != ActualAccount) - break; - PostQuitMessage(0); - return 1; - - case WM_YAMN_NOTIFYICON: - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - - switch (lParam) { - case WM_LBUTTONDBLCLK: - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { - return 0; - } - - if (ActualAccount->AbilityFlags & YAMN_ACC_BROWSE) { - ShowWindow(hDlg, SW_SHOWNORMAL); - SetForegroundWindow(hDlg); - } - else DestroyWindow(hDlg); - - ReadDoneFcn(ActualAccount->AccountAccessSO); - break; - } - break; - - case WM_YAMN_SHOWSELECTED: - { - int iSelect = SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_GETNEXTITEM, -1, MAKELPARAM((UINT)LVNI_FOCUSED, 0)); // return item selected - if (iSelect != -1) { - LV_ITEMW item; - - item.iItem = iSelect; - item.iSubItem = 0; - item.mask = LVIF_PARAM | LVIF_STATE; - item.stateMask = 0xFFFFFFFF; - ListView_GetItem(GetDlgItem(hDlg, IDC_LISTMAILS), &item); - HYAMNMAIL ActualMail = (HYAMNMAIL)item.lParam; - if (nullptr != ActualMail) { - PYAMN_MAILSHOWPARAM MailParam = new YAMN_MAILSHOWPARAM; - MailParam->account = GetWindowAccount(hDlg); - MailParam->mail = ActualMail; - mir_forkthread(ShowEmailThread, MailParam); - } - } - } - break; - - case WM_SYSCOMMAND: - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - switch (wParam) { - case SC_CLOSE: - DestroyWindow(hDlg); - break; - } - break; - - case WM_COMMAND: - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - - switch (LOWORD(wParam)) { - case IDC_BTNCHECKALL: - ListView_SetItemState(GetDlgItem(hDlg, IDC_LISTMAILS), -1, 0, LVIS_SELECTED); // deselect all items - ListView_SetItemState(GetDlgItem(hDlg, IDC_LISTMAILS), -1, LVIS_SELECTED, LVIS_SELECTED); - Items = ListView_GetItemCount(GetDlgItem(hDlg, IDC_LISTMAILS)); - ListView_RedrawItems(GetDlgItem(hDlg, IDC_LISTMAILS), 0, Items); - UpdateWindow(GetDlgItem(hDlg, IDC_LISTMAILS)); - SetFocus(GetDlgItem(hDlg, IDC_LISTMAILS)); - break; - - case IDC_BTNOK: - DestroyWindow(hDlg); - break; - - case IDC_BTNAPP: - { - PROCESS_INFORMATION pi; - STARTUPINFOW si; - - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - if (ActualAccount->NewMailN.App != nullptr) { - wchar_t *Command; - if (ActualAccount->NewMailN.AppParam != nullptr) - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; - else - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; - - if (Command != nullptr) { - mir_wstrcpy(Command, L"\""); - mir_wstrcat(Command, ActualAccount->NewMailN.App); - mir_wstrcat(Command, L"\" "); - if (ActualAccount->NewMailN.AppParam != nullptr) - mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); - CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); - delete[] Command; - } - } - - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - - if (!(GetKeyState(VK_SHIFT) & 0x8000) && !(GetKeyState(VK_CONTROL) & 0x8000)) - DestroyWindow(hDlg); - } - break; - - case IDC_BTNDEL: - { - HYAMNMAIL ActualMail; - uint32_t Total = 0; - - // we use event to signal, that running thread has all needed stack parameters copied - HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (ThreadRunningEV == nullptr) - break; - - Items = ListView_GetItemCount(GetDlgItem(hDlg, IDC_LISTMAILS)); - - LVITEM item; - item.stateMask = 0xFFFFFFFF; - - if (WAIT_OBJECT_0 == WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { - for (int i = 0; i < Items; i++) { - item.iItem = i; - item.iSubItem = 0; - item.mask = LVIF_PARAM | LVIF_STATE; - item.stateMask = 0xFFFFFFFF; - ListView_GetItem(GetDlgItem(hDlg, IDC_LISTMAILS), &item); - ActualMail = (HYAMNMAIL)item.lParam; - if (nullptr == ActualMail) - break; - if (item.state & LVIS_SELECTED) { - ActualMail->Flags |= YAMN_MSG_USERDELETE; //set to mail we are going to delete it - Total++; - } - } - - // Enable write-access to mails - WriteDoneFcn(ActualAccount->MessagesAccessSO); - - if (Total) { - wchar_t DeleteMsg[1024]; - - mir_snwprintf(DeleteMsg, TranslateT("Do you really want to delete %d selected mails?"), Total); - if (IDOK == MessageBox(hDlg, DeleteMsg, TranslateT("Delete confirmation"), MB_OKCANCEL | MB_ICONWARNING)) { - struct DeleteParam ParamToDeleteMails = {YAMN_DELETEVERSION, ThreadRunningEV, ActualAccount, nullptr}; - - // Find if there's mail marked to delete, which was deleted before - if (WAIT_OBJECT_0 == WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { - for (ActualMail = (HYAMNMAIL)ActualAccount->Mails; ActualMail != nullptr; ActualMail = ActualMail->Next) { - if ((ActualMail->Flags & YAMN_MSG_DELETED) && ((ActualMail->Flags & YAMN_MSG_USERDELETE))) //if selected mail was already deleted - { - DeleteMessageFromQueueFcn((HYAMNMAIL *)&ActualAccount->Mails, ActualMail, 1); - CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)ActualAccount->Plugin, (LPARAM)ActualMail); //delete it from memory - continue; - } - } - // Set flag to marked mails that they can be deleted - SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY | YAMN_MSG_USERDELETE, 0, YAMN_MSG_DELETEOK, 1); - // Create new thread which deletes marked mails. - HANDLE NewThread = mir_forkthread(ActualAccount->Plugin->Fcn->DeleteMailsFcnPtr, &ParamToDeleteMails); - if (NewThread != nullptr) - WaitForSingleObject(ThreadRunningEV, INFINITE); - - // Enable write-access to mails - WriteDoneFcn(ActualAccount->MessagesAccessSO); - } - } - else //else mark messages that they are not to be deleted - SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY | YAMN_MSG_USERDELETE, 0, YAMN_MSG_USERDELETE, 0); - } - } - CloseHandle(ThreadRunningEV); - if (g_plugin.getByte(YAMN_CLOSEDELETE, 0)) - DestroyWindow(hDlg); - } - break; - } - break; - - case WM_SIZE: - if (wParam == SIZE_RESTORED) { - LONG x = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; - LONG y = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; - MoveWindow(GetDlgItem(hDlg, IDC_BTNDEL), 5, y - 5 - 25, (x - 20) / 3, 25, TRUE); //where to put DELETE button while resizing - MoveWindow(GetDlgItem(hDlg, IDC_BTNCHECKALL), 10 + (x - 20) / 3, y - 5 - 25, (x - 20) / 6, 25, TRUE); //where to put CHECK ALL button while resizing - MoveWindow(GetDlgItem(hDlg, IDC_BTNAPP), 15 + (x - 20) / 3 + (x - 20) / 6, y - 5 - 25, (x - 20) / 3, 25, TRUE); //where to put RUN APP button while resizing - MoveWindow(GetDlgItem(hDlg, IDC_BTNOK), 20 + 2 * (x - 20) / 3 + (x - 20) / 6, y - 5 - 25, (x - 20) / 6, 25, TRUE); //where to put OK button while resizing - MoveWindow(GetDlgItem(hDlg, IDC_LISTMAILS), 5, 5, x - 10, y - 55, TRUE); //where to put list mail window while resizing - MoveWindow(GetDlgItem(hDlg, IDC_STSTATUS), 5, y - 5 - 45, x - 10, 15, TRUE); //where to put account status text while resizing - } - return 0; - - case WM_GETMINMAXINFO: - ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = MAILBROWSER_MINXSIZE; - ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = MAILBROWSER_MINYSIZE; - return 0; - - case WM_TIMER: - { - NOTIFYICONDATA nid; - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - - memset(&nid, 0, sizeof(nid)); - nid.cbSize = sizeof(NOTIFYICONDATA); - nid.hWnd = hDlg; - nid.uID = 0; - nid.uFlags = NIF_ICON; - if (mwui->TrayIconState == 0) - nid.hIcon = g_plugin.getIcon(IDI_CHECKMAIL); - else - nid.hIcon = g_plugin.getIcon(IDI_NEWMAIL); - Shell_NotifyIcon(NIM_MODIFY, &nid); - mwui->TrayIconState = !mwui->TrayIconState; - // UpdateWindow(hDlg); - } - break; - - case WM_NOTIFY: - switch (((LPNMHDR)lParam)->idFrom) { - case IDC_LISTMAILS: - switch (((LPNMHDR)lParam)->code) { - case NM_DBLCLK: - SendMessage(hDlg, WM_YAMN_SHOWSELECTED, 0, 0); - break; - - case LVN_COLUMNCLICK: - if (nullptr != (ActualAccount = GetWindowAccount(hDlg))) { - NM_LISTVIEW *pNMListView = (NM_LISTVIEW *)lParam; - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - switch ((int)pNMListView->iSubItem) { - case 0: - bFrom = !bFrom; - break; - case 1: - bSub = !bSub; - break; - case 2: - bSize = !bSize; - break; - case 3: - bDate = !bDate; - break; - default: - break; - } - ListView_SortItems(pNMListView->hdr.hwndFrom, ListViewCompareProc, pNMListView->iSubItem); - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - } - break; - - case NM_CUSTOMDRAW: - { - LPNMLVCUSTOMDRAW cd = (LPNMLVCUSTOMDRAW)lParam; - LONG_PTR PaintCode; - - if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) - break; - - switch (cd->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - PaintCode = CDRF_NOTIFYITEMDRAW; - break; - case CDDS_ITEMPREPAINT: - PaintCode = CDRF_NOTIFYSUBITEMDRAW; - break; - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: - { - BOOL umma; - { - struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); - umma = mwui->UpdateMailsMessagesAccess; - } - HYAMNMAIL ActualMail = (HYAMNMAIL)cd->nmcd.lItemlParam; - if (!ActualMail) - ActualMail = (HYAMNMAIL)readItemLParam(cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec); - - if (!umma) - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->MessagesAccessSO)) - return 0; - - switch (ActualMail->Flags & YAMN_MSG_SPAMMASK) { - case YAMN_MSG_SPAML1: - case YAMN_MSG_SPAML2: - cd->clrText = RGB(150, 150, 150); - break; - case YAMN_MSG_SPAML3: - cd->clrText = RGB(200, 200, 200); - cd->clrTextBk = RGB(160, 160, 160); - break; - case 0: - if (cd->nmcd.dwItemSpec & 1) - cd->clrTextBk = RGB(230, 230, 230); - break; - default: - break; - } - if (ActualMail->Flags & YAMN_MSG_UNSEEN) - cd->clrTextBk = RGB(220, 235, 250); - PaintCode = CDRF_DODEFAULT; - - if (!umma) - ReadDoneFcn(ActualAccount->MessagesAccessSO); - break; - } - default: - PaintCode = 0; - } - SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PaintCode); - return 1; - } - } - } - break; - - case WM_CONTEXTMENU: - if (GetWindowLongPtr((HWND)wParam, GWLP_ID) == IDC_LISTMAILS) { - //MessageBox(0,"LISTHEADERS","Debug",0); - HWND hList = GetDlgItem(hDlg, IDC_LISTMAILS); - POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - if (pt.x == -1) pt.x = 0; - if (pt.y == -1) pt.y = 0; - if (int numRows = ListView_GetItemCount(hList)) { - HMENU hMenu = CreatePopupMenu(); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy Selected")); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy All")); - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); - int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hDlg, nullptr); - DestroyMenu(hMenu); - if (nReturnCmd > 0) { - int courRow = 0; - size_t sizeNeeded = 0; - wchar_t from[128] = {0}, subject[256] = {0}, size[16] = {0}, date[64] = {0}; - for (courRow = 0; courRow < numRows; courRow++) { - if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; - ListView_GetItemText(hList, courRow, 0, from, _countof(from)); - ListView_GetItemText(hList, courRow, 1, subject, _countof(subject)); - ListView_GetItemText(hList, courRow, 2, size, _countof(size)); - ListView_GetItemText(hList, courRow, 3, date, _countof(date)); - sizeNeeded += 5 + mir_wstrlen(from) + mir_wstrlen(subject) + mir_wstrlen(size) + mir_wstrlen(date); - } - if (sizeNeeded && OpenClipboard(hDlg)) { - EmptyClipboard(); - HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE, (sizeNeeded + 1) * sizeof(wchar_t)); - wchar_t *buff = (wchar_t *)GlobalLock(hData); - int courPos = 0; - for (courRow = 0; courRow < numRows; courRow++) { - if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; - ListView_GetItemText(hList, courRow, 0, from, _countof(from)); - ListView_GetItemText(hList, courRow, 1, subject, _countof(subject)); - ListView_GetItemText(hList, courRow, 2, size, _countof(size)); - ListView_GetItemText(hList, courRow, 3, date, _countof(date)); - courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"%s\t%s\t%s\t%s\r\n", from, subject, size, date); - } - GlobalUnlock(hData); - - SetClipboardData(CF_UNICODETEXT, hData); - - CloseClipboard(); - } - } - } - } - break; // just in case - } - return 0; -} - -LRESULT CALLBACK ListViewSubclassProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - HWND hwndParent = GetParent(hDlg); - - switch (msg) { - case WM_GETDLGCODE: - { - LPMSG lpmsg = (LPMSG)lParam; - if (lpmsg != nullptr) { - if (lpmsg->message == WM_KEYDOWN - && lpmsg->wParam == VK_RETURN) - return DLGC_WANTALLKEYS; - } - } - break; - - case WM_KEYDOWN: - { - BOOL isCtrl = GetKeyState(VK_CONTROL) & 0x8000; - BOOL isShift = GetKeyState(VK_SHIFT) & 0x8000; - BOOL isAlt = GetKeyState(VK_MENU) & 0x8000; - - switch (wParam) { - case 'A': // ctrl-a - if (!isAlt && !isShift && isCtrl) SendMessage(hwndParent, WM_COMMAND, IDC_BTNCHECKALL, 0); - break; - case VK_RETURN: - case VK_SPACE: - if (!isAlt && !isShift && !isCtrl) SendMessage(hwndParent, WM_YAMN_SHOWSELECTED, 0, 0); - break; - case VK_DELETE: - SendMessage(hwndParent, WM_COMMAND, IDC_BTNDEL, 0); - break; - } - } - break; - } - return mir_callNextSubclass(hDlg, ListViewSubclassProc, msg, wParam, lParam); -} - -void __cdecl MailBrowser(void *Param) -{ - MSG msg; - - HWND hMailBrowser; - BOOL WndFound = FALSE; - - struct MailBrowserWinParam MyParam = *(struct MailBrowserWinParam *)Param; - CAccount *ActualAccount = MyParam.account; - SCIncFcn(ActualAccount->UsingThreads); - - // we will not use params in stack anymore - SetEvent(MyParam.ThreadRunningEV); - - __try { - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) - return; - - if (!(ActualAccount->AbilityFlags & YAMN_ACC_BROWSE)) { - MyParam.nflags = MyParam.nflags & ~YAMN_ACC_MSG; - MyParam.nnflags = MyParam.nnflags & ~YAMN_ACC_MSG; - } - - if (!(ActualAccount->AbilityFlags & YAMN_ACC_POPUP)) - MyParam.nflags = MyParam.nflags & ~YAMN_ACC_POP; - - ReadDoneFcn(ActualAccount->AccountAccessSO); - - if (nullptr != (hMailBrowser = WindowList_Find(YAMNVar.NewMailAccountWnd, (UINT_PTR)ActualAccount))) - WndFound = TRUE; - - if ((hMailBrowser == nullptr) && ((MyParam.nflags & YAMN_ACC_MSG) || (MyParam.nflags & YAMN_ACC_ICO) || (MyParam.nnflags & YAMN_ACC_MSG))) { - hMailBrowser = CreateDialogParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_DLGVIEWMESSAGES), nullptr, DlgProcYAMNMailBrowser, (LPARAM)&MyParam); - Window_SetIcon_IcoLib(hMailBrowser, g_plugin.getIconHandle(IDI_NEWMAIL)); - MoveWindow(hMailBrowser, PosX, PosY, SizeX, SizeY, TRUE); - } - - if (hMailBrowser != nullptr) { - struct CChangeContent Params = {MyParam.nflags, MyParam.nnflags}; //if this thread created window, just post message to update mails - - SendMessage(hMailBrowser, WM_YAMN_CHANGECONTENT, (WPARAM)ActualAccount, (LPARAM)&Params); //we ensure this will do the thread who created the browser window - } - else - UpdateMails(nullptr, ActualAccount, MyParam.nflags, MyParam.nnflags); //update mails without displaying or refreshing any window - - if ((hMailBrowser != nullptr) && !WndFound) { //we process message loop only for thread that created window - while (GetMessage(&msg, nullptr, 0, 0)) { - if (hMailBrowser == nullptr || !IsDialogMessage(hMailBrowser, &msg)) { /* Wine fix. */ - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - } - - if ((!WndFound) && (ActualAccount->Plugin->Fcn != nullptr) && (ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr != nullptr) && ActualAccount->AbleToWork) - ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr(); - } - __finally { - SCDecFcn(ActualAccount->UsingThreads); - } -} - -INT_PTR RunMailBrowserSvc(WPARAM wParam, LPARAM lParam) -{ - PYAMN_MAILBROWSERPARAM Param = (PYAMN_MAILBROWSERPARAM)wParam; - - if ((uint32_t)lParam != YAMN_MAILBROWSERVERSION) - return 0; - - //an event for successfull copy parameters to which point a pointer in stack for new thread - HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); - Param->ThreadRunningEV = ThreadRunningEV; - - HANDLE NewThread = mir_forkthread(MailBrowser, Param); - if (NewThread != nullptr) - WaitForSingleObject(ThreadRunningEV, INFINITE); - - CloseHandle(ThreadRunningEV); - return 1; -} +/* + * This code implements window handling (new mail) + * + * (c) majvan 2002-2004 + */ + /* There can be problems when compiling this file, because in this file + * we are using both unicode and no-unicode functions and compiler does not + * like it in one file + * When you got errors, try to comment the #define and compile, then + * put it back to uncommented and compile again :) + */ + +#include "../stdafx.h" + +#define TIMER_FLASHING 0x09061979 +#define MAILBROWSER_MINXSIZE 200 //min size of mail browser window +#define MAILBROWSER_MINYSIZE 130 + +#define MAILBROWSERTITLE LPGEN("%s - %d new mail messages, %d total") + +void __cdecl ShowEmailThread(void *Param); + +//-------------------------------------------------------------------------------------------------- +char *s_MonthNames[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +bool bDate = false, bSub = false, bSize = false, bFrom = false; +int PosX = 0, PosY = 0, SizeX = 460, SizeY = 100; +int HeadSizeX = 0x2b2, HeadSizeY = 0x0b5, HeadPosX = 100, HeadPosY = 100; +int HeadSplitPos = 250; // per-mils of the size +static int FromWidth = 250, SubjectWidth = 280, SizeWidth = 50, SizeDate = 205; +unsigned char optDateTime = (SHOWDATELONG | SHOWDATENOTODAY); + +struct CMailNumbersSub +{ + int Total; //any mail + int New; //uses YAMN_MSG_NEW flag + int UnSeen; //uses YAMN_MSG_UNSEEN flag + // int Browser; //uses YAMN_MSG_BROWSER flag + int BrowserUC; //uses YAMN_MSG_BROWSER flag and YAMN_MSG_UNSEEN flag + int Display; //uses YAMN_MSG_DISPLAY flag + int DisplayTC; //uses YAMN_MSG_DISPLAY flag and YAMN_MSG_DISPLAYC flag + int DisplayUC; //uses YAMN_MSG_DISPLAY flag and YAMN_MSG_DISPLAYC flag and YAMN_MSG_UNSEEN flag + int Popup; //uses YAMN_MSG_POPUP flag + int PopupTC; //uses YAMN_MSG_POPUPC flag + int PopupNC; //uses YAMN_MSG_POPUPC flag and YAMN_MSG_NEW flag + int PopupRun; //uses YAMN_MSG_POPUP flag and YAMN_MSG_NEW flag + int PopupSL2NC; //uses YAMN_MSG_SPAML2 flag and YAMN_MSG_NEW flag + int PopupSL3NC; //uses YAMN_MSG_SPAML3 flag and YAMN_MSG_NEW flag + // int SysTray; //uses YAMN_MSG_SYSTRAY flag + int SysTrayUC; //uses YAMN_MSG_SYSTRAY flag and YAMN_MSG_UNSEEN flag + // int Sound; //uses YAMN_MSG_SOUND flag + int SoundNC; //uses YAMN_MSG_SOUND flag and YAMN_MSG_NEW flag + // int App; //uses YAMN_MSG_APP flag + int AppNC; //uses YAMN_MSG_APP flag and YAMN_MSG_NEW flag + int EventNC; //uses YAMN_MSG_NEVENT flag and YAMN_MSG_NEW flag +}; + +struct CMailNumbers +{ + struct CMailNumbersSub Real; + struct CMailNumbersSub Virtual; +}; + +struct CMailWinUserInfo +{ + CAccount *Account; + int TrayIconState; + BOOL UpdateMailsMessagesAccess; + BOOL Seen; + BOOL RunFirstTime; +}; + +struct CChangeContent +{ + uint32_t nflags; + uint32_t nnflags; +}; + +struct CUpdateMails +{ + struct CChangeContent *Flags; + BOOL Waiting; + HANDLE Copied; +}; + +struct CSortList +{ + HWND hDlg; + int iSubItem; +}; + +// Retrieves CAccount *, whose mails are displayed in ListMails +// hLM- handle of dialog window +// returns handle of account +inline CAccount *GetWindowAccount(HWND hDialog); + +// Looks to mail flags and increment mail counter (e.g. if mail is new, increments the new mail counter +// msgq- mail, which increments the counters +// MN- counnters structure +void IncrementMailCounters(HYAMNMAIL msgq, struct CMailNumbers *MN); + +enum +{ + UPDATE_FAIL = 0, //function failed + UPDATE_NONE, //none update has been performed + UPDATE_OK, //some changes occured, update performed +}; + +// Just looks for mail changes in account and update the mail browser window +// hDlg- dialog handle +// ActualAccount- account handle +// nflags- flags what to do when new mail arrives +// nnflags- flags what to do when no new mail arrives +// returns one of UPDATE_XXX value(not implemented yet) +int UpdateMails(HWND hDlg, CAccount *ActualAccount, uint32_t nflags, uint32_t nnflags); + +// When new mail occurs, shows window, plays sound, runs application... +// hDlg- dialog handle. Dialog of mailbrowser is already created and actions are performed over this window +// ActualAccount- handle of account, whose mails are to be notified +// MN- statistics of mails in account +// nflags- what to do or not to do (e.g. to show mailbrowser window or prohibit to show) +// nflags- flags what to do when new mail arrives +// nnflags- flags what to do when no new mail arrives +void DoMailActions(HWND hDlg, CAccount *ActualAccount, struct CMailNumbers *MN, uint32_t nflags, uint32_t nnflags); + +// Looks for items in mailbrowser and if they were deleted, delete them from browser window +// hListView- handle of listview window +// ActualAccount- handle of account, whose mails are show +// MailNumbers- pointer to structure, in which function stores numbers of mails with some property +// returns one of UPDATE_XXX value (not implemented yet) +int ChangeExistingMailStatus(HWND hListView, CAccount *ActualAccount); + +// Adds new mails to ListView and if any new, shows multi popup (every new message is new popup window created by popup plugin) +// hListView- handle of listview window +// ActualAccount- handle of account, whose mails are show +// NewMailPopup- pointer to prepared structure for popup plugin, can be NULL if no popup show +// MailNumbers- pointer to structure, in which function stores numbers of mails with some property +// nflags- flags what to do when new mail arrives +// returns one of UPDATE_XXX value (not implemented yet) +int AddNewMailsToListView(HWND hListView, CAccount *ActualAccount, uint32_t nflags); + +// Window callback procedure for popup window (created by popup plugin) +LRESULT CALLBACK NewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// Window callback procedure for popup window (created by popup plugin) +LRESULT CALLBACK NoNewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// Dialog callback procedure for mail browser +INT_PTR CALLBACK DlgProcYAMNMailBrowser(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +// MailBrowser thread function creates window if needed, tray icon and plays sound +void __cdecl MailBrowser(void *Param); + +LRESULT CALLBACK ListViewSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// Runs mail browser in new thread +INT_PTR RunMailBrowserSvc(WPARAM, LPARAM); + +#define YAMN_BROWSER_SHOWPOPUP 0x01 + +// list view items' order criteria +#define LVORDER_NOORDER -1 +#define LVORDER_STRING 0 +#define LVORDER_NUMERIC 1 +#define LVORDER_DATETIME 2 + +// list view order direction +#define LVORDER_ASCENDING 1 +#define LVORDER_NONE 0 +#define LVORDER_DESCENDING -1 + +// list view sort type +#define LVSORTPRIORITY_NONE -1 + +// List view column info. +typedef struct _SAMPLELISTVIEWCOLUMN +{ + UINT uCXCol; // index + int nSortType; // sorting type (STRING = 0, NUMERIC, DATE, DATETIME) + int nSortOrder; // sorting order (ASCENDING = -1, NONE, DESCENDING) + int nPriority; // sort priority (-1 for none, 0, 1, ..., nColumns - 1 maximum) + wchar_t lpszName[128]; // column name +} SAMPLELISTVIEWCOLUMN; + +// Compare priority +typedef struct _LVCOMPAREINFO +{ + int iIdx; // Index + int iPriority; // Priority +} LVCOMPAREINFO, *LPLVCOMPAREINFO; + +//-------------------------------------------------------------------------------------------------- + +LPARAM readItemLParam(HWND hwnd, uint32_t iItem) +{ + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = iItem; + item.iSubItem = 0; + SendMessage(hwnd, LVM_GETITEM, 0, (LPARAM)&item); + return item.lParam; +} + +inline CAccount *GetWindowAccount(HWND hDlg) +{ + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + + return (mwui == nullptr) ? nullptr : mwui->Account; +} + +void IncrementMailCounters(HYAMNMAIL msgq, struct CMailNumbers *MN) +{ + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.Total++; + else + MN->Real.Total++; + + if (msgq->Flags & YAMN_MSG_NEW) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.New++; + else + MN->Real.New++; + if (msgq->Flags & YAMN_MSG_UNSEEN) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.UnSeen++; + else + MN->Real.UnSeen++; + if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_BROWSER)) == (YAMN_MSG_UNSEEN | YAMN_MSG_BROWSER)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.BrowserUC++; + else + MN->Real.BrowserUC++; + if (msgq->Flags & YAMN_MSG_DISPLAY) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.Display++; + else + MN->Real.Display++; + if ((msgq->Flags & (YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) == (YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.DisplayTC++; + else + MN->Real.DisplayTC++; + if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) == (YAMN_MSG_UNSEEN | YAMN_MSG_DISPLAYC | YAMN_MSG_DISPLAY)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.DisplayUC++; + else + MN->Real.DisplayUC++; + if (msgq->Flags & YAMN_MSG_POPUP) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.Popup++; + else + MN->Real.Popup++; + if ((msgq->Flags & YAMN_MSG_POPUPC) == YAMN_MSG_POPUPC) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.PopupTC++; + else + MN->Real.PopupTC++; + if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_POPUPC)) == (YAMN_MSG_NEW | YAMN_MSG_POPUPC)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.PopupNC++; + else + MN->Real.PopupNC++; + if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_POPUP)) == (YAMN_MSG_NEW | YAMN_MSG_POPUP)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.PopupRun++; + else + MN->Real.PopupRun++; + if ((msgq->Flags & YAMN_MSG_NEW) && YAMN_MSG_SPAML(msgq->Flags, YAMN_MSG_SPAML2)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.PopupSL2NC++; + else + MN->Real.PopupSL2NC++; + if ((msgq->Flags & YAMN_MSG_NEW) && YAMN_MSG_SPAML(msgq->Flags, YAMN_MSG_SPAML3)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.PopupSL3NC++; + else + MN->Real.PopupSL3NC++; + /* if (msgq->MailData->Flags & YAMN_MSG_SYSTRAY) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.SysTray++; + else + MN->Real.SysTray++; + */ if ((msgq->Flags & (YAMN_MSG_UNSEEN | YAMN_MSG_SYSTRAY)) == (YAMN_MSG_UNSEEN | YAMN_MSG_SYSTRAY)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.SysTrayUC++; + else + MN->Real.SysTrayUC++; + /* if (msgq->MailData->Flags & YAMN_MSG_SOUND) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.Sound++; + else + MN->Real.Sound++; + */ if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_SOUND)) == (YAMN_MSG_NEW | YAMN_MSG_SOUND)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.SoundNC++; + else + MN->Real.SoundNC++; + /* if (msgq->MailData->Flags & YAMN_MSG_APP) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.App++; + else + MN->Real.App++; + */ if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_APP)) == (YAMN_MSG_NEW | YAMN_MSG_APP)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.AppNC++; + else + MN->Real.AppNC++; + if ((msgq->Flags & (YAMN_MSG_NEW | YAMN_MSG_NEVENT)) == (YAMN_MSG_NEW | YAMN_MSG_NEVENT)) + if (msgq->Flags & YAMN_MSG_VIRTUAL) + MN->Virtual.EventNC++; + else + MN->Real.EventNC++; +} + +int UpdateMails(HWND hDlg, CAccount *ActualAccount, uint32_t nflags, uint32_t nnflags) +{ + struct CMailNumbers MN; + + BOOL Loaded; + BOOL RunMailBrowser, RunPopups; + + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + //now we ensure read access for account and write access for its mails + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { + PostMessage(hDlg, WM_DESTROY, 0, 0); + return UPDATE_FAIL; + } + + if (WAIT_OBJECT_0 != WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + PostMessage(hDlg, WM_DESTROY, 0, 0); + return UPDATE_FAIL; + } + + memset(&MN, 0, sizeof(MN)); + + for (HYAMNMAIL msgq = (HYAMNMAIL)ActualAccount->Mails; msgq != nullptr; msgq = msgq->Next) { + if (!LoadedMailData(msgq)) //check if mail is already in memory + { + Loaded = false; + if (nullptr == LoadMailData(msgq)) //if we could not load mail to memory, consider this mail deleted and do not display it + continue; + } + else + Loaded = true; + + IncrementMailCounters(msgq, &MN); + + if (!Loaded) + UnloadMailData(msgq); //do not keep data for mail in memory + } + + if (mwui != nullptr) + mwui->UpdateMailsMessagesAccess = TRUE; + + //Now we are going to check if extracting data from mail headers are needed. + //If popups will be displayed or mailbrowser window + if ((((mwui != nullptr) && !(mwui->RunFirstTime)) && + ( + ((nnflags & YAMN_ACC_MSGP) && !(MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || + ((nflags & YAMN_ACC_MSGP) && (MN.Real.BrowserUC + MN.Virtual.BrowserUC)) + ) + ) || //if mail window was displayed before and flag YAMN_ACC_MSGP is set + ((nnflags & YAMN_ACC_MSG) && !(MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || //if needed to run mailbrowser when no unseen and no unseen mail found + ((nflags & YAMN_ACC_MSG) && (MN.Real.BrowserUC + MN.Virtual.BrowserUC)) || //if unseen mails found, we sure run mailbrowser + ((nflags & YAMN_ACC_ICO) && (MN.Real.SysTrayUC + MN.Virtual.SysTrayUC)) + ) //if needed to run systray + RunMailBrowser = TRUE; + else + RunMailBrowser = FALSE; + + // if some popups with mails are needed to show + if ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (MN.Real.PopupNC + MN.Virtual.PopupNC)) + RunPopups = TRUE; + else RunPopups = FALSE; + + if (RunMailBrowser) + ChangeExistingMailStatus(GetDlgItem(hDlg, IDC_LISTMAILS), ActualAccount); + if (RunMailBrowser || RunPopups) + AddNewMailsToListView(hDlg == nullptr ? nullptr : GetDlgItem(hDlg, IDC_LISTMAILS), ActualAccount, nflags); + + if (RunMailBrowser) { + size_t len = mir_strlen(ActualAccount->Name) + mir_strlen(Translate(MAILBROWSERTITLE)) + 10; //+10 chars for numbers + char *TitleStrA = new char[len]; + wchar_t *TitleStrW = new wchar_t[len]; + + mir_snprintf(TitleStrA, len, Translate(MAILBROWSERTITLE), ActualAccount->Name, MN.Real.DisplayUC + MN.Virtual.DisplayUC, MN.Real.Display + MN.Virtual.Display); + MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, TitleStrA, -1, TitleStrW, (int)mir_strlen(TitleStrA) + 1); + SetWindowTextW(hDlg, TitleStrW); + delete[] TitleStrA; + delete[] TitleStrW; + } + + DoMailActions(hDlg, ActualAccount, &MN, nflags, nnflags); + + SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_NEW, 0, YAMN_MSG_NEW, YAMN_FLAG_REMOVE); //rempve the new flag + if (!RunMailBrowser) + SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_UNSEEN, YAMN_MSG_STAYUNSEEN, YAMN_MSG_UNSEEN, YAMN_FLAG_REMOVE); //remove the unseen flag when it was not displayed and it has not "stay unseen" flag set + + if (mwui != nullptr) { + mwui->UpdateMailsMessagesAccess = FALSE; + mwui->RunFirstTime = FALSE; + } + + WriteDoneFcn(ActualAccount->MessagesAccessSO); + ReadDoneFcn(ActualAccount->AccountAccessSO); + + if (RunMailBrowser) + UpdateWindow(GetDlgItem(hDlg, IDC_LISTMAILS)); + else if (hDlg != nullptr) + DestroyWindow(hDlg); + + return 1; +} + +int ChangeExistingMailStatus(HWND hListView, CAccount *ActualAccount) +{ + LVITEM item; + HYAMNMAIL mail, msgq; + + int in = ListView_GetItemCount(hListView); + item.mask = LVIF_PARAM; + + for (int i = 0; i < in; i++) { + item.iItem = i; + item.iSubItem = 0; + if (TRUE == ListView_GetItem(hListView, &item)) + mail = (HYAMNMAIL)item.lParam; + else + continue; + for (msgq = (HYAMNMAIL)ActualAccount->Mails; (msgq != nullptr) && (msgq != mail); msgq = msgq->Next); //found the same mail in account queue + if (msgq == nullptr) //if mail was not found + if (TRUE == ListView_DeleteItem(hListView, i)) { + in--; i--; + continue; + } + } + + return TRUE; +} + +void MimeDateToLocalizedDateTime(char *datein, wchar_t *dateout, int lendateout); +int AddNewMailsToListView(HWND hListView, CAccount *ActualAccount, uint32_t nflags) +{ + wchar_t *FromStr; + wchar_t SizeStr[20]; + wchar_t LocalDateStr[128]; + + LVITEMW item; + LVFINDINFO fi; + + int foundi = 0, lfoundi = 0; + struct CHeader UnicodeHeader; + BOOL Loaded, Extracted, FromStrNew = FALSE; + + memset(&item, 0, sizeof(item)); + memset(&UnicodeHeader, 0, sizeof(UnicodeHeader)); + + if (hListView != nullptr) { + item.mask = LVIF_TEXT | LVIF_PARAM; + item.iItem = 0; + memset(&fi, 0, sizeof(fi)); + fi.flags = LVFI_PARAM; //let's go search item by lParam number + lfoundi = 0; + } + + POPUPDATAW NewMailPopup = {}; + NewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; + NewMailPopup.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); + if (nflags & YAMN_ACC_POPC) { + NewMailPopup.colorBack = ActualAccount->NewMailN.PopupB; + NewMailPopup.colorText = ActualAccount->NewMailN.PopupT; + } + else { + NewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); + NewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + NewMailPopup.iSeconds = ActualAccount->NewMailN.PopupTime; + + NewMailPopup.PluginWindowProc = NewMailPopupProc; + NewMailPopup.PluginData = nullptr; //it's new mail popup + + for (HYAMNMAIL msgq = (HYAMNMAIL)ActualAccount->Mails; msgq != nullptr; msgq = msgq->Next, lfoundi++) { + // now we hide mail pointer to item's lParam member. We can later use it to retrieve mail datas + + Extracted = FALSE; FromStr = nullptr; FromStrNew = FALSE; + + if (hListView != nullptr) { + fi.lParam = (LPARAM)msgq; + if (-1 != (foundi = ListView_FindItem(hListView, -1, &fi))) { // if mail is already in window + lfoundi = foundi; + continue; // do not insert any item + } + + item.iItem = lfoundi; // insert after last found item + item.lParam = (LPARAM)msgq; + } + + if (!LoadedMailData(msgq)) { // check if mail is already in memory + Loaded = false; + if (nullptr == LoadMailData(msgq)) //if we could not load mail to memory, consider this mail deleted and do not display it + continue; + } + else Loaded = true; + + if (((hListView != nullptr) && (msgq->Flags & YAMN_MSG_DISPLAY)) || + ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (msgq->Flags & YAMN_MSG_POPUP) && (msgq->Flags & YAMN_MSG_NEW))) { + + if (!Extracted) ExtractHeader(msgq->MailData->TranslatedHeader, msgq->MailData->CP, &UnicodeHeader); + Extracted = TRUE; + + if ((UnicodeHeader.From != nullptr) && (UnicodeHeader.FromNick != nullptr)) { + size_t size = mir_wstrlen(UnicodeHeader.From) + mir_wstrlen(UnicodeHeader.FromNick) + 4; + FromStr = new wchar_t[size]; + mir_snwprintf(FromStr, size, L"%s <%s>", UnicodeHeader.FromNick, UnicodeHeader.From); + FromStrNew = TRUE; + } + else if (UnicodeHeader.From != nullptr) + FromStr = UnicodeHeader.From; + else if (UnicodeHeader.FromNick != nullptr) + FromStr = UnicodeHeader.FromNick; + else if (UnicodeHeader.ReturnPath != nullptr) + FromStr = UnicodeHeader.ReturnPath; + + if (nullptr == FromStr) { + FromStr = L""; + FromStrNew = FALSE; + } + } + + if ((hListView != nullptr) && (msgq->Flags & YAMN_MSG_DISPLAY)) { + item.iSubItem = 0; + item.pszText = FromStr; + item.iItem = SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&item); + + item.iSubItem = 1; + item.pszText = (nullptr != UnicodeHeader.Subject ? UnicodeHeader.Subject : (wchar_t *)L""); + SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); + + item.iSubItem = 2; + mir_snwprintf(SizeStr, L"%d kB", msgq->MailData->Size / 1024); + item.pszText = SizeStr; + SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); + + item.iSubItem = 3; + item.pszText = L""; + + for (CMimeItem *heads = msgq->MailData->TranslatedHeader; heads != nullptr; heads = heads->Next) { + if (!_stricmp(heads->name, "Date")) { + MimeDateToLocalizedDateTime(heads->value, LocalDateStr, 128); + item.pszText = LocalDateStr; + break; + } + } + SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); + } + + if ((nflags & YAMN_ACC_POP) && (ActualAccount->Flags & YAMN_ACC_POPN) && (msgq->Flags & YAMN_MSG_POPUP) && (msgq->Flags & YAMN_MSG_NEW)) { + mir_wstrncpy(NewMailPopup.lpwzContactName, FromStr, _countof(NewMailPopup.lpwzContactName)); + mir_wstrncpy(NewMailPopup.lpwzText, UnicodeHeader.Subject, _countof(NewMailPopup.lpwzText)); + + PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM)malloc(sizeof(YAMN_MAILSHOWPARAM)); + if (MailParam) { + MailParam->account = ActualAccount; + MailParam->mail = msgq; + MailParam->ThreadRunningEV = nullptr; + NewMailPopup.PluginData = MailParam; + PUAddPopupW(&NewMailPopup); + } + } + + if ((msgq->Flags & YAMN_MSG_UNSEEN) && (ActualAccount->NewMailN.Flags & YAMN_ACC_KBN)) + CallService(MS_KBDNOTIFY_EVENTSOPENED, 1, NULL); + + if (FromStrNew) + delete[] FromStr; + + if (Extracted) { + DeleteHeaderContent(&UnicodeHeader); + memset(&UnicodeHeader, 0, sizeof(UnicodeHeader)); + } + + if (!Loaded) { + SaveMailData(msgq); + UnloadMailData(msgq); //do not keep data for mail in memory + } + } + + return TRUE; +} + +void DoMailActions(HWND hDlg, CAccount *ActualAccount, struct CMailNumbers *MN, uint32_t nflags, uint32_t nnflags) +{ + NOTIFYICONDATA nid = {}; + nid.cbSize = sizeof(nid); + nid.hWnd = hDlg; + + if (MN->Real.EventNC + MN->Virtual.EventNC) + NotifyEventHooks(hNewMailHook, 0, 0); + + if ((nflags & YAMN_ACC_KBN) && (MN->Real.PopupRun + MN->Virtual.PopupRun)) + CallService(MS_KBDNOTIFY_STARTBLINK, (WPARAM)MN->Real.PopupNC + MN->Virtual.PopupNC, NULL); + + if ((nflags & YAMN_ACC_CONT) && (MN->Real.PopupRun + MN->Virtual.PopupRun)) { + wchar_t tszMsg[250]; + mir_snwprintf(tszMsg, TranslateT("%s : %d new mail message(s), %d total"), _A2T(ActualAccount->Name).get(), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); + + if (!(nflags & YAMN_ACC_CONTNOEVENT)) { + CLISTEVENT evt = {}; + evt.flags = CLEF_UNICODE; + evt.hContact = ActualAccount->hContact; + evt.hIcon = g_plugin.getIcon(IDI_NEWMAIL); + evt.hDbEvent = ActualAccount->hContact; + evt.lParam = ActualAccount->hContact; + evt.pszService = MS_YAMN_CLISTDBLCLICK; + evt.szTooltip.w = tszMsg; + g_clistApi.pfnAddEvent(&evt); + } + db_set_ws(ActualAccount->hContact, "CList", "StatusMsg", tszMsg); + + if (nflags & YAMN_ACC_CONTNICK) + g_plugin.setWString(ActualAccount->hContact, "Nick", tszMsg); + } + + if ((nflags & YAMN_ACC_POP) && + !(ActualAccount->Flags & YAMN_ACC_POPN) && + (MN->Real.PopupRun + MN->Virtual.PopupRun)) { + POPUPDATAW NewMailPopup; + + NewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; + NewMailPopup.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); + if (nflags & YAMN_ACC_POPC) { + NewMailPopup.colorBack = ActualAccount->NewMailN.PopupB; + NewMailPopup.colorText = ActualAccount->NewMailN.PopupT; + } + else { + NewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); + NewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + NewMailPopup.iSeconds = ActualAccount->NewMailN.PopupTime; + + NewMailPopup.PluginWindowProc = NewMailPopupProc; + NewMailPopup.PluginData = (void *)nullptr; //multiple popups + + mir_wstrncpy(NewMailPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(NewMailPopup.lpwzContactName)); + mir_snwprintf(NewMailPopup.lpwzText, TranslateT("%d new mail message(s), %d total"), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); + PUAddPopupW(&NewMailPopup); + } + + // destroy tray icon if no new mail + if ((MN->Real.SysTrayUC + MN->Virtual.SysTrayUC == 0) && (hDlg != nullptr)) + Shell_NotifyIcon(NIM_DELETE, &nid); + + // and remove the event + if ((nflags & YAMN_ACC_CONT) && (!(nflags & YAMN_ACC_CONTNOEVENT)) && (MN->Real.UnSeen + MN->Virtual.UnSeen == 0)) + g_clistApi.pfnRemoveEvent(ActualAccount->hContact, ActualAccount->hContact); + + if ((MN->Real.BrowserUC + MN->Virtual.BrowserUC == 0) && (hDlg != nullptr)) { + if (!IsWindowVisible(hDlg) && !(nflags & YAMN_ACC_MSG)) + PostMessage(hDlg, WM_DESTROY, 0, 0); //destroy window if no new mail and window is not visible + if (nnflags & YAMN_ACC_MSG) //if no new mail and msg should be executed + { + SetForegroundWindow(hDlg); + ShowWindow(hDlg, SW_SHOWNORMAL); + } + } + else + if (hDlg != nullptr) //else insert icon and set window if new mails + { + SendDlgItemMessageW(hDlg, IDC_LISTMAILS, LVM_SCROLL, 0, (LPARAM)0x7ffffff); + + if ((nflags & YAMN_ACC_ICO) && (MN->Real.SysTrayUC + MN->Virtual.SysTrayUC)) { + nid.hIcon = g_plugin.getIcon(IDI_NEWMAIL); + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = WM_YAMN_NOTIFYICON; + mir_snwprintf(nid.szTip, L"%S %s", ActualAccount->Name, TranslateT("- new mail message(s)")); + Shell_NotifyIcon(NIM_ADD, &nid); + SetTimer(hDlg, TIMER_FLASHING, 500, nullptr); + } + if (nflags & YAMN_ACC_MSG) //if no new mail and msg should be executed + ShowWindow(hDlg, SW_SHOWNORMAL); + } + + if (MN->Real.AppNC + MN->Virtual.AppNC != 0) { + if (nflags & YAMN_ACC_APP) { + PROCESS_INFORMATION pi; + STARTUPINFOW si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + if (ActualAccount->NewMailN.App != nullptr) { + wchar_t *Command; + if (ActualAccount->NewMailN.AppParam != nullptr) + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; + else + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; + + if (Command != nullptr) { + mir_wstrcpy(Command, L"\""); + mir_wstrcat(Command, ActualAccount->NewMailN.App); + mir_wstrcat(Command, L"\" "); + if (ActualAccount->NewMailN.AppParam != nullptr) + mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); + CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + delete[] Command; + } + } + } + } + + if (MN->Real.SoundNC + MN->Virtual.SoundNC != 0) + if (nflags & YAMN_ACC_SND) + Skin_PlaySound(YAMN_NEWMAILSOUND); + + if ((nnflags & YAMN_ACC_POP) && (MN->Real.PopupRun + MN->Virtual.PopupRun == 0)) { + POPUPDATAW NoNewMailPopup = {}; + + NoNewMailPopup.lchContact = (ActualAccount->hContact != NULL) ? ActualAccount->hContact : (UINT_PTR)ActualAccount; + NoNewMailPopup.lchIcon = g_plugin.getIcon(IDI_LAUNCHAPP); + if (nflags & YAMN_ACC_POPC) { + NoNewMailPopup.colorBack = ActualAccount->NoNewMailN.PopupB; + NoNewMailPopup.colorText = ActualAccount->NoNewMailN.PopupT; + } + else { + NoNewMailPopup.colorBack = GetSysColor(COLOR_BTNFACE); + NoNewMailPopup.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + NoNewMailPopup.iSeconds = ActualAccount->NoNewMailN.PopupTime; + + NoNewMailPopup.PluginWindowProc = NoNewMailPopupProc; + NoNewMailPopup.PluginData = nullptr; //it's not new mail popup + + mir_wstrncpy(NoNewMailPopup.lpwzContactName, _A2T(ActualAccount->Name), _countof(NoNewMailPopup.lpwzContactName)); + if (MN->Real.PopupSL2NC + MN->Virtual.PopupSL2NC) + mir_snwprintf(NoNewMailPopup.lpwzText, TranslateT("No new mail message, %d spam(s)"), MN->Real.PopupSL2NC + MN->Virtual.PopupSL2NC); + else + mir_wstrncpy(NoNewMailPopup.lpwzText, TranslateT("No new mail message"), _countof(NoNewMailPopup.lpwzText)); + PUAddPopupW(&NoNewMailPopup); + } + + if ((nflags & YAMN_ACC_CONT) && (MN->Real.PopupRun + MN->Virtual.PopupRun == 0)) { + if (ActualAccount->hContact != NULL) { + if (MN->Real.PopupTC + MN->Virtual.PopupTC) { + char tmp[255]; + mir_snprintf(tmp, Translate("%d new mail message(s), %d total"), MN->Real.PopupNC + MN->Virtual.PopupNC, MN->Real.PopupTC + MN->Virtual.PopupTC); + db_set_s(ActualAccount->hContact, "CList", "StatusMsg", tmp); + } + else db_set_s(ActualAccount->hContact, "CList", "StatusMsg", Translate("No new mail message")); + + if (nflags & YAMN_ACC_CONTNICK) + g_plugin.setString(ActualAccount->hContact, "Nick", ActualAccount->Name); + } + } + return; +} + +LRESULT CALLBACK NewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + INT_PTR PluginParam = 0; + switch (msg) { + case WM_COMMAND: + // if clicked and it's new mail popup window + if ((HIWORD(wParam) == STN_CLICKED) && (-1 != (PluginParam = (INT_PTR)PUGetPluginData(hWnd)))) { + MCONTACT hContact = 0; + CAccount *Account; + if (PluginParam) { + PYAMN_MAILSHOWPARAM MailParam = new YAMN_MAILSHOWPARAM; + memcpy(MailParam, (PINT_PTR)PluginParam, sizeof(YAMN_MAILSHOWPARAM)); + hContact = MailParam->account->hContact; + Account = MailParam->account; + mir_forkthread(ShowEmailThread, MailParam); + } + else { + DBVARIANT dbv; + + hContact = PUGetContact(hWnd); + + if (!g_plugin.getString(hContact, "Id", &dbv)) { + Account = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + db_free(&dbv); + } + else Account = (CAccount *)hContact; //???? + + if (WAIT_OBJECT_0 == WaitToReadFcn(Account->AccountAccessSO)) { + switch (msg) { + case WM_COMMAND: + { + YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, Account, + (Account->NewMailN.Flags & ~YAMN_ACC_POP) | YAMN_ACC_MSGP | YAMN_ACC_MSG, + (Account->NoNewMailN.Flags & ~YAMN_ACC_POP) | YAMN_ACC_MSGP | YAMN_ACC_MSG}; + + RunMailBrowserSvc((WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); + } + break; + } + ReadDoneFcn(Account->AccountAccessSO); + } + } + if ((Account->NewMailN.Flags & YAMN_ACC_CONT) && !(Account->NewMailN.Flags & YAMN_ACC_CONTNOEVENT)) + g_clistApi.pfnRemoveEvent(hContact, hContact); + } + __fallthrough; + + case WM_CONTEXTMENU: + PUDeletePopup(hWnd); + break; + case UM_FREEPLUGINDATA: + { + PYAMN_MAILSHOWPARAM mpd = (PYAMN_MAILSHOWPARAM)PUGetPluginData(hWnd); + if ((mpd) && (INT_PTR)mpd != -1)free(mpd); + return FALSE; + } + case UM_INITPOPUP: + //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. + WindowList_Add(YAMNVar.MessageWnds, hWnd); + break; + case UM_DESTROYPOPUP: + WindowList_Remove(YAMNVar.MessageWnds, hWnd); + break; + case WM_YAMN_STOPACCOUNT: + { + CAccount *ActualAccount; + DBVARIANT dbv; + + MCONTACT hContact = PUGetContact(hWnd); + + if (!g_plugin.getString(hContact, "Id", &dbv)) { + ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + db_free(&dbv); + } + else + ActualAccount = (CAccount *)hContact; + + if ((CAccount *)wParam != ActualAccount) + break; + DestroyWindow(hWnd); + return 0; + } + case WM_NOTIFY: + default: + break; + } + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +LRESULT CALLBACK NoNewMailPopupProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_COMMAND: + if ((HIWORD(wParam) == STN_CLICKED) && (msg == WM_COMMAND)) { + CAccount *ActualAccount; + DBVARIANT dbv; + + MCONTACT hContact = PUGetContact(hWnd); + + if (!g_plugin.getString(hContact, "Id", &dbv)) { + ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + db_free(&dbv); + } + else + ActualAccount = (CAccount *)hContact; + + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + switch (msg) { + case WM_COMMAND: + { + YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualAccount->NewMailN.Flags, ActualAccount->NoNewMailN.Flags, nullptr}; + + Param.nnflags = Param.nnflags | YAMN_ACC_MSG; //show mails in account even no new mail in account + Param.nnflags = Param.nnflags & ~YAMN_ACC_POP; + + Param.nflags = Param.nflags | YAMN_ACC_MSG; //show mails in account even no new mail in account + Param.nflags = Param.nflags & ~YAMN_ACC_POP; + + RunMailBrowserSvc((WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); + } + break; + } + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + PUDeletePopup(hWnd); + } + break; + + case WM_CONTEXTMENU: + PUDeletePopup(hWnd); + break; + + case UM_FREEPLUGINDATA: + //Here we'd free our own data, if we had it. + return FALSE; + case UM_INITPOPUP: + //This is the equivalent to WM_INITDIALOG you'd get if you were the maker of dialog popups. + WindowList_Add(YAMNVar.MessageWnds, hWnd); + break; + case UM_DESTROYPOPUP: + WindowList_Remove(YAMNVar.MessageWnds, hWnd); + break; + case WM_YAMN_STOPACCOUNT: + { + CAccount *ActualAccount; + DBVARIANT dbv; + + MCONTACT hContact = PUGetContact(hWnd); + + if (!g_plugin.getString(hContact, "Id", &dbv)) { + ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + db_free(&dbv); + } + else + ActualAccount = (CAccount *)hContact; + + if ((CAccount *)wParam != ActualAccount) + break; + + DestroyWindow(hWnd); + return 0; + } + } + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +#ifdef __GNUC__ +//number of 100 ns periods between FILETIME 0 (1601/01/01 00:00:00.0000000) and TIMESTAMP 0 (1970/01/01 00:00:00) +#define NUM100NANOSEC 116444736000000000ULL +//The biggest time Get[Date|Time]Format can handle (Fri, 31 Dec 30827 23:59:59.9999999) +#define MAXFILETIME 0x7FFF35F4F06C7FFFULL +#else +#define NUM100NANOSEC 116444736000000000 +#define MAXFILETIME 0x7FFF35F4F06C7FFF +#endif + +ULONGLONG MimeDateToFileTime(char *datein) +{ + char *day = nullptr, *month = nullptr, *year = nullptr, *time = nullptr, *shift = nullptr; + SYSTEMTIME st; + ULONGLONG res = 0; + int wShiftSeconds = TimeZone_ToLocal(0); + GetLocalTime(&st); + //datein = "Xxx, 1 Jan 2060 5:29:1 +0530 XXX"; + //datein = "Xxx, 1 Jan 2060 05:29:10 "; + //datein = " ManySpaces 1.5 Jan 2060 05::"; + //datein = "Xxx, 35 February 20 :29:10 "; + //datein = "01.12.2007 (22:38:17)"; // + if (datein) { + char tmp[64]; + while (datein[0] == ' ') datein++; // eat leading spaces + strncpy(tmp, datein, 63); tmp[63] = 0; + if (atoi(tmp)) { // Parseable integer on DayOfWeek field? Buggy mime date. + day = tmp; + } + else { + int i = 0; + while (tmp[i] == ' ')i++; if (day = strchr(&tmp[i], ' ')) { day[0] = 0; day++; } + } + if (day) { while (day[0] == ' ') day++; if (month = strchr(day, ' ')) { month[0] = 0; month++; } } + if (month) { while (month[0] == ' ')month++; if (year = strchr(month, ' ')) { year[0] = 0; year++; } } + if (year) { while (year[0] == ' ') year++; if (time = strchr(year, ' ')) { time[0] = 0; time++; } } + if (time) { while (time[0] == ' ') time++; if (shift = strchr(time, ' ')) { shift[0] = 0; shift++; shift[5] = 0; } } + + if (year) { + st.wYear = atoi(year); + if (mir_strlen(year) < 4) if (st.wYear < 70)st.wYear += 2000; else st.wYear += 1900; + }; + if (month) for (int i = 0; i < 12; i++) if (strncmp(month, s_MonthNames[i], 3) == 0) { st.wMonth = i + 1; break; } + if (day) st.wDay = atoi(day); + if (time) { + char *h, *m, *s; + h = time; + if (m = strchr(h, ':')) { + m[0] = 0; m++; + if (s = strchr(m, ':')) { s[0] = 0; s++; } + } + else s = nullptr; + st.wHour = atoi(h); + st.wMinute = m ? atoi(m) : 0; + st.wSecond = s ? atoi(s) : 0; + } + else { st.wHour = st.wMinute = st.wSecond = 0; } + + if (shift) { + if (mir_strlen(shift) < 4) { + //has only hour + wShiftSeconds = (atoi(shift)) * 3600; + } + else { + char *smin = shift + mir_strlen(shift) - 2; + int ismin = atoi(smin); + smin[0] = 0; + int ishour = atoi(shift); + wShiftSeconds = (ishour * 60 + (ishour < 0 ? -1 : 1) * ismin) * 60; + } + } + } // if (datein) + FILETIME ft; + if (SystemTimeToFileTime(&st, &ft)) { + res = ((ULONGLONG)ft.dwHighDateTime << 32) | ((ULONGLONG)ft.dwLowDateTime); + LONGLONG w100nano = Int32x32To64((uint32_t)wShiftSeconds, 10000000); + res -= w100nano; + } + else { + res = 0; + } + return res; +} + +void FileTimeToLocalizedDateTime(LONGLONG filetime, wchar_t *dateout, int lendateout) +{ + int localeID = Langpack_GetDefaultLocale(); + //int localeID = MAKELCID(LANG_URDU, SORT_DEFAULT); + if (localeID == CALLSERVICE_NOTFOUND) localeID = LOCALE_USER_DEFAULT; + if (filetime > MAXFILETIME) filetime = MAXFILETIME; + else if (filetime <= 0) { + wcsncpy(dateout, TranslateT("Invalid"), lendateout); + return; + } + SYSTEMTIME st; + uint16_t wTodayYear = 0, wTodayMonth = 0, wTodayDay = 0; + FILETIME ft; + BOOL willShowDate = !(optDateTime & SHOWDATENOTODAY); + if (!willShowDate) { + GetLocalTime(&st); + wTodayYear = st.wYear; + wTodayMonth = st.wMonth; + wTodayDay = st.wDay; + } + ft.dwLowDateTime = (uint32_t)filetime; + ft.dwHighDateTime = (uint32_t)(filetime >> 32); + FILETIME localft; + if (!FileTimeToLocalFileTime(&ft, &localft)) { + // this should never happen + wcsncpy(dateout, L"Incorrect FileTime", lendateout); + } + else { + if (!FileTimeToSystemTime(&localft, &st)) { + // this should never happen + wcsncpy(dateout, L"Incorrect LocalFileTime", lendateout); + } + else { + dateout[lendateout - 1] = 0; + int templen = 0; + if (!willShowDate) willShowDate = (wTodayYear != st.wYear) || (wTodayMonth != st.wMonth) || (wTodayDay != st.wDay); + if (willShowDate) { + templen = GetDateFormatW(localeID, (optDateTime & SHOWDATELONG) ? DATE_LONGDATE : DATE_SHORTDATE, &st, nullptr, dateout, lendateout - 2); + dateout[templen - 1] = ' '; + } + if (templen < (lendateout - 1)) { + GetTimeFormatW(localeID, (optDateTime & SHOWDATENOSECONDS) ? TIME_NOSECONDS : 0, &st, nullptr, &dateout[templen], lendateout - templen - 1); + } + } + } +} + +void MimeDateToLocalizedDateTime(char *datein, wchar_t *dateout, int lendateout) +{ + ULONGLONG ft = MimeDateToFileTime(datein); + FileTimeToLocalizedDateTime(ft, dateout, lendateout); +} + +int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + if (lParam1 == NULL || lParam2 == NULL) + return 0; + + int nResult = 0; + char *str1; + char *str2; + HYAMNMAIL email1 = (HYAMNMAIL)lParam1; + HYAMNMAIL email2 = (HYAMNMAIL)lParam2; + struct CShortHeader Header1; + struct CShortHeader Header2; + memset(&Header1, 0, sizeof(Header1)); + memset(&Header2, 0, sizeof(Header2)); + + try { + ExtractShortHeader(email1->MailData->TranslatedHeader, &Header1); + ExtractShortHeader(email2->MailData->TranslatedHeader, &Header2); + + switch ((int)lParamSort) { + case 0: //From + if (Header1.FromNick == nullptr) + str1 = Header1.From; + else str1 = Header1.FromNick; + + if (Header2.FromNick == nullptr) + str2 = Header2.From; + else str2 = Header2.FromNick; + + nResult = mir_strcmp(str1, str2); + + if (bFrom) nResult = -nResult; + break; + case 1: //Subject + if (Header1.Subject == nullptr) + str1 = " "; + else str1 = Header1.Subject; + + if (Header2.Subject == nullptr) + str2 = " "; + else str2 = Header2.Subject; + + nResult = mir_strcmp(str1, str2); + + if (bSub) nResult = -nResult; + break; + case 2: //Size + if (email1->MailData->Size == email2->MailData->Size) nResult = 0; + if (email1->MailData->Size > email2->MailData->Size) nResult = 1; + if (email1->MailData->Size < email2->MailData->Size) nResult = -1; + + if (bSize) nResult = -nResult; + break; + + case 3: //Date + { + ULONGLONG ts1 = 0, ts2 = 0; + ts1 = MimeDateToFileTime(Header1.Date); + ts2 = MimeDateToFileTime(Header2.Date); + if (ts1 > ts2) nResult = 1; + else if (ts1 < ts2) nResult = -1; + else nResult = 0; + } + if (bDate) nResult = -nResult; + break; + + default: + if (Header1.Subject == nullptr) str1 = " "; + else str1 = Header1.Subject; + + if (Header2.Subject == nullptr) str2 = " "; + else str2 = Header2.Subject; + + nResult = mir_strcmp(str1, str2); + break; + } + //MessageBox(NULL,str1,str2,0); + } + catch (...) { + } + + //free mem + DeleteShortHeaderContent(&Header1); + DeleteShortHeaderContent(&Header2); + return nResult; + +} + +HCURSOR hCurSplitNS, hCurSplitWE; +#define DM_SPLITTERMOVED (WM_USER+15) + +static LRESULT CALLBACK SplitterSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_NCHITTEST: + return HTCLIENT; + + case WM_SETCURSOR: + SetCursor(hCurSplitNS); + return TRUE; + + case WM_LBUTTONDOWN: + SetCapture(hwnd); + return 0; + + case WM_MOUSEMOVE: + if (GetCapture() == hwnd) { + RECT rc; + GetClientRect(hwnd, &rc); + SendMessage(GetParent(hwnd), DM_SPLITTERMOVED, (short)HIWORD(GetMessagePos()) + rc.bottom / 2, (LPARAM)hwnd); + } + return 0; + + case WM_LBUTTONUP: + ReleaseCapture(); + return 0; + } + return mir_callNextSubclass(hwnd, SplitterSubclassProc, msg, wParam, lParam); +} + +void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); +int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); + +INT_PTR CALLBACK DlgProcYAMNShowMessage(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM)lParam; + wchar_t *iHeaderW = nullptr; + wchar_t *iValueW = nullptr; + int StrLen; + HWND hListView = GetDlgItem(hDlg, IDC_LISTHEADERS); + mir_subclassWindow(GetDlgItem(hDlg, IDC_SPLITTER), SplitterSubclassProc); + SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)MailParam); + Window_SetIcon_IcoLib(hDlg, g_plugin.getIconHandle(IDI_NEWMAIL)); + + ListView_SetUnicodeFormat(hListView, TRUE); + ListView_SetExtendedListViewStyle(hListView, LVS_EX_FULLROWSELECT); + + StrLen = MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Header"), -1, nullptr, 0); + iHeaderW = new wchar_t[StrLen + 1]; + MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Header"), -1, iHeaderW, StrLen); + + StrLen = MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Value"), -1, nullptr, 0); + iValueW = new wchar_t[StrLen + 1]; + MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, Translate("Value"), -1, iValueW, StrLen); + + LVCOLUMN lvc0 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, 130, iHeaderW, 0, 0}; + LVCOLUMN lvc1 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, 400, iValueW, 0, 0}; + SendMessage(hListView, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc0); + SendMessage(hListView, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc1); + if (nullptr != iHeaderW) + delete[] iHeaderW; + if (nullptr != iValueW) + delete[] iValueW; + + SendMessage(hDlg, WM_YAMN_CHANGECONTENT, 0, (LPARAM)MailParam); + MoveWindow(hDlg, HeadPosX, HeadPosY, HeadSizeX, HeadSizeY, 0); + ShowWindow(hDlg, SW_SHOWNORMAL); + } + break; + + case WM_YAMN_CHANGECONTENT: + { + PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM) + (lParam ? lParam : GetWindowLongPtr(hDlg, DWLP_USER)); + HWND hListView = GetDlgItem(hDlg, IDC_LISTHEADERS); + HWND hEdit = GetDlgItem(hDlg, IDC_EDITBODY); + //do not redraw + SendMessage(hListView, WM_SETREDRAW, 0, 0); + ListView_DeleteAllItems(hListView); + struct CMimeItem *Header; + LVITEMW item; + item.mask = LVIF_TEXT | LVIF_PARAM; + wchar_t *From = nullptr, *Subj = nullptr; + char *contentType = nullptr, *transEncoding = nullptr, *body = nullptr; //should not be delete[]-ed + for (Header = MailParam->mail->MailData->TranslatedHeader; Header != nullptr; Header = Header->Next) { + wchar_t *str1 = nullptr; + wchar_t *str2 = nullptr; + wchar_t str_nul[2] = {0}; + if (!body) if (!_stricmp(Header->name, "Body")) { body = Header->value; continue; } + if (!contentType) if (!_stricmp(Header->name, "Content-Type")) contentType = Header->value; + if (!transEncoding) if (!_stricmp(Header->name, "Content-Transfer-Encoding")) transEncoding = Header->value; + //ConvertCodedStringToUnicode(Header->name,&str1,MailParam->mail->MailData->CP,1); + { + int streamsize = MultiByteToWideChar(20127, 0, Header->name, -1, nullptr, 0); + str1 = (wchar_t *)malloc(sizeof(wchar_t) * (streamsize + 1)); + MultiByteToWideChar(20127, 0, Header->name, -1, str1, streamsize);//US-ASCII + } + ConvertCodedStringToUnicode(Header->value, &str2, MailParam->mail->MailData->CP, 1); + if (!str2) { str2 = (wchar_t *)str_nul; }// the header value may be NULL + if (!From) if (!_stricmp(Header->name, "From")) { + From = new wchar_t[mir_wstrlen(str2) + 1]; + mir_wstrcpy(From, str2); + } + if (!Subj) if (!_stricmp(Header->name, "Subject")) { + Subj = new wchar_t[mir_wstrlen(str2) + 1]; + mir_wstrcpy(Subj, str2); + } + //if (!hasBody) if (!mir_strcmp(Header->name,"Body")) hasBody = true; + int count = 0; wchar_t **split = nullptr; + int ofs = 0; + while (str2[ofs]) { + if ((str2[ofs] == 0x266A) || (str2[ofs] == 0x25D9) || (str2[ofs] == 0x25CB) || + (str2[ofs] == 0x09) || (str2[ofs] == 0x0A) || (str2[ofs] == 0x0D))count++; + ofs++; + } + split = new wchar_t *[count + 1]; + count = 0; ofs = 0; + split[0] = str2; + while (str2[ofs]) { + if ((str2[ofs] == 0x266A) || (str2[ofs] == 0x25D9) || (str2[ofs] == 0x25CB) || + (str2[ofs] == 0x09) || (str2[ofs] == 0x0A) || (str2[ofs] == 0x0D)) { + if (str2[ofs - 1]) { + count++; + } + split[count] = (wchar_t *)(str2 + ofs + 1); + str2[ofs] = 0; + } + ofs++; + }; + + if (!_stricmp(Header->name, "From") || !_stricmp(Header->name, "To") || !_stricmp(Header->name, "Date") || !_stricmp(Header->name, "Subject")) + item.iItem = 0; + else + item.iItem = 999; + for (int i = 0; i <= count; i++) { + item.iSubItem = 0; + if (i == 0) + item.pszText = str1; + else { + item.iItem++; + item.pszText = nullptr; + } + item.iItem = SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&item); + item.iSubItem = 1; + item.pszText = str2 ? split[i] : nullptr; + SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)item.iItem, (LPARAM)&item); + } + delete[] split; + + if (str1) + free(str1); + if (str2 != (wchar_t *)str_nul) + free(str2); + } + if (body) { + wchar_t *bodyDecoded = nullptr; + char *localBody = nullptr; + if (contentType) { + if (!_strnicmp(contentType, "text", 4)) { + if (transEncoding) { + if (!_stricmp(transEncoding, "base64")) { + int size = (int)mir_strlen(body) * 3 / 4 + 5; + localBody = new char[size + 1]; + DecodeBase64(body, localBody, size); + } + else if (!_stricmp(transEncoding, "quoted-printable")) { + int size = (int)mir_strlen(body) + 2; + localBody = new char[size + 1]; + DecodeQuotedPrintable(body, localBody, size, FALSE); + } + } + } + else if (!_strnicmp(contentType, "multipart/", 10)) { + char *bondary = nullptr; + if (nullptr != (bondary = ExtractFromContentType(contentType, "boundary="))) { + bodyDecoded = ParseMultipartBody(body, bondary); + delete[] bondary; + } + } + } + if (!bodyDecoded)ConvertStringToUnicode(localBody ? localBody : body, MailParam->mail->MailData->CP, &bodyDecoded); + SetWindowTextW(hEdit, bodyDecoded); + delete[] bodyDecoded; + if (localBody) delete[] localBody; + SetFocus(hEdit); + } + if (!(MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED)) { + MailParam->mail->Flags |= YAMN_MSG_BODYREQUESTED; + CallService(MS_YAMN_ACCOUNTCHECK, (WPARAM)MailParam->account, 0); + } + else { + if (MailParam->mail->Flags & YAMN_MSG_UNSEEN) { + MailParam->mail->Flags &= ~YAMN_MSG_UNSEEN; //mark the message as seen + HWND hMailBrowser = WindowList_Find(YAMNVar.NewMailAccountWnd, (UINT_PTR)MailParam->account); + if (hMailBrowser) { + struct CChangeContent Params = {MailParam->account->NewMailN.Flags | YAMN_ACC_MSGP, MailParam->account->NoNewMailN.Flags | YAMN_ACC_MSGP}; + SendMessage(hMailBrowser, WM_YAMN_CHANGECONTENT, (WPARAM)MailParam->account, (LPARAM)&Params); + } + else UpdateMails(nullptr, MailParam->account, MailParam->account->NewMailN.Flags, MailParam->account->NoNewMailN.Flags); + } + } + ShowWindow(GetDlgItem(hDlg, IDC_SPLITTER), (MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED) ? SW_SHOW : SW_HIDE); + ShowWindow(hEdit, (MailParam->mail->Flags & YAMN_MSG_BODYRECEIVED) ? SW_SHOW : SW_HIDE); + wchar_t *title = nullptr; + size_t size = (From ? mir_wstrlen(From) : 0) + (Subj ? mir_wstrlen(Subj) : 0) + 4; + title = new wchar_t[size]; + if (From && Subj) + mir_snwprintf(title, size, L"%s (%s)", Subj, From); + else if (From) + wcsncpy_s(title, size, From, _TRUNCATE); + else if (Subj) + wcsncpy_s(title, size, Subj, _TRUNCATE); + else + wcsncpy_s(title, size, L"none", _TRUNCATE); + if (Subj) delete[] Subj; + if (From) delete[] From; + SetWindowTextW(hDlg, title); + delete[] title; + // turn on redrawing + SendMessage(hListView, WM_SETREDRAW, 1, 0); + SendMessage(hDlg, WM_SIZE, 0, HeadSizeY << 16 | HeadSizeX); + } break; + + case WM_YAMN_STOPACCOUNT: + { + PYAMN_MAILSHOWPARAM MailParam = (PYAMN_MAILSHOWPARAM) + (lParam ? lParam : GetWindowLongPtr(hDlg, DWLP_USER)); + + if (nullptr == MailParam) + break; + if ((CAccount *)wParam != MailParam->account) + break; + + DestroyWindow(hDlg); + } + return 1; + + case WM_CTLCOLORSTATIC: + // here should be check if this is our edittext control. + // but we have only one static control (for now); + SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); + SetTextColor((HDC)wParam, GetSysColor(COLOR_WINDOWTEXT)); + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hDlg); + { + RECT coord; + if (GetWindowRect(hDlg, &coord)) { + HeadPosX = coord.left; + HeadSizeX = coord.right - coord.left; + HeadPosY = coord.top; + HeadSizeY = coord.bottom - coord.top; + } + + PostQuitMessage(1); + } + break; + + case WM_SYSCOMMAND: + switch (wParam) { + case SC_CLOSE: + DestroyWindow(hDlg); + break; + } + break; + + case WM_MOVE: + HeadPosX = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; + HeadPosY = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; + return 0; + + case DM_SPLITTERMOVED: + if ((HWND)lParam == GetDlgItem(hDlg, IDC_SPLITTER)) { + POINT pt; + pt.x = 0; + pt.y = wParam; + ScreenToClient(hDlg, &pt); + HeadSplitPos = (pt.y * 1000) / HeadSizeY;//+rc.bottom-rc.top; + if (HeadSplitPos >= 1000) HeadSplitPos = 999; + else if (HeadSplitPos <= 0) HeadSplitPos = 1; + else SendMessage(hDlg, WM_SIZE, 0, HeadSizeY << 16 | HeadSizeX); + } + return 0; + + case WM_SIZE: + if (wParam == SIZE_RESTORED) { + HWND hList = GetDlgItem(hDlg, IDC_LISTHEADERS); + HWND hEdit = GetDlgItem(hDlg, IDC_EDITBODY); + BOOL isBodyShown = ((PYAMN_MAILSHOWPARAM)(GetWindowLongPtr(hDlg, DWLP_USER)))->mail->Flags & YAMN_MSG_BODYRECEIVED; + HeadSizeX = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; + HeadSizeY = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; + int localSplitPos = (HeadSplitPos * HeadSizeY) / 1000; + int localSizeX; + RECT coord; + MoveWindow(GetDlgItem(hDlg, IDC_SPLITTER), 5, localSplitPos, HeadSizeX - 10, 2, TRUE); + MoveWindow(hEdit, 5, localSplitPos + 6, HeadSizeX - 10, HeadSizeY - localSplitPos - 11, TRUE); //where to put text window while resizing + MoveWindow(hList, 5, 5, HeadSizeX - 10, (isBodyShown ? localSplitPos : HeadSizeY) - 10, TRUE); //where to put headers list window while resizing + //if (changeX) { + if (GetClientRect(hList, &coord)) { + localSizeX = coord.right - coord.left; + } + else localSizeX = HeadSizeX; + LONG iNameWidth = ListView_GetColumnWidth(hList, 0); + ListView_SetColumnWidth(hList, 1, (localSizeX <= iNameWidth) ? 0 : (localSizeX - iNameWidth)); + //} + } + return 0; + + case WM_CONTEXTMENU: + if (GetWindowLongPtr((HWND)wParam, GWLP_ID) == IDC_LISTHEADERS) { + //MessageBox(0,"LISTHEADERS","Debug",0); + HWND hList = GetDlgItem(hDlg, IDC_LISTHEADERS); + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + if (pt.x == -1) pt.x = 0; + if (pt.y == -1) pt.y = 0; + if (int numRows = ListView_GetItemCount(hList)) { + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy Selected")); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy All")); + AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); + int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hDlg, nullptr); + DestroyMenu(hMenu); + if (nReturnCmd > 0) { + int courRow = 0; + size_t sizeNeeded = 0; + wchar_t headname[64] = {0}, headvalue[256] = {0}; + for (courRow = 0; courRow < numRows; courRow++) { + if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; + ListView_GetItemText(hList, courRow, 0, headname, _countof(headname)); + ListView_GetItemText(hList, courRow, 1, headvalue, _countof(headvalue)); + size_t headnamelen = mir_wstrlen(headname); + if (headnamelen) sizeNeeded += 1 + headnamelen; + sizeNeeded += 3 + mir_wstrlen(headvalue); + } + if (sizeNeeded && OpenClipboard(hDlg)) { + EmptyClipboard(); + HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE, (sizeNeeded + 1) * sizeof(wchar_t)); + wchar_t *buff = (wchar_t *)GlobalLock(hData); + int courPos = 0; + for (courRow = 0; courRow < numRows; courRow++) { + if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; + ListView_GetItemText(hList, courRow, 0, headname, _countof(headname)); + ListView_GetItemText(hList, courRow, 1, headvalue, _countof(headvalue)); + if (mir_wstrlen(headname)) courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"%s:\t%s\r\n", headname, headvalue); + else courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"\t%s\r\n", headvalue); + } + GlobalUnlock(hData); + + SetClipboardData(CF_UNICODETEXT, hData); + + CloseClipboard(); + } + } + } + } + break; // just in case + } + return 0; +} + +void __cdecl ShowEmailThread(void *Param) +{ + struct MailShowMsgWinParam MyParam = *(struct MailShowMsgWinParam *)Param; + + SCIncFcn(MyParam.account->UsingThreads); + + if (MyParam.mail->MsgWindow) { + //if (!BringWindowToTop(MyParam.mail->MsgWindow)) { + if (!SetForegroundWindow(MyParam.mail->MsgWindow)) { + SendMessage(MyParam.mail->MsgWindow, WM_DESTROY, 0, 0); + MyParam.mail->MsgWindow = nullptr; + goto CREADTEVIEWMESSAGEWINDOW; + } + + if (IsIconic(MyParam.mail->MsgWindow)) + OpenIcon(MyParam.mail->MsgWindow); + } + else { +CREADTEVIEWMESSAGEWINDOW: + MyParam.mail->MsgWindow = CreateDialogParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_DLGSHOWMESSAGE), nullptr, DlgProcYAMNShowMessage, (LPARAM)&MyParam); + WindowList_Add(YAMNVar.MessageWnds, MyParam.mail->MsgWindow); + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) { + if (MyParam.mail->MsgWindow == nullptr || !IsDialogMessage(MyParam.mail->MsgWindow, &msg)) { /* Wine fix. */ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + WindowList_Remove(YAMNVar.MessageWnds, MyParam.mail->MsgWindow); + MyParam.mail->MsgWindow = nullptr; + } + + SCDecFcn(MyParam.account->UsingThreads); + delete (struct MailShowMsgWinParam *)Param; +} + +INT_PTR CALLBACK DlgProcYAMNMailBrowser(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CAccount *ActualAccount; + int Items; + + switch (msg) { + case WM_INITDIALOG: + { + struct MailBrowserWinParam *MyParam = (struct MailBrowserWinParam *)lParam; + + ListView_SetUnicodeFormat(GetDlgItem(hDlg, IDC_LISTMAILS), TRUE); + ListView_SetExtendedListViewStyle(GetDlgItem(hDlg, IDC_LISTMAILS), LVS_EX_FULLROWSELECT); + + ActualAccount = MyParam->account; + struct CMailWinUserInfo *mwui = new struct CMailWinUserInfo; + mwui->Account = ActualAccount; + mwui->TrayIconState = 0; + mwui->UpdateMailsMessagesAccess = FALSE; + mwui->Seen = FALSE; + mwui->RunFirstTime = TRUE; + + SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)mwui); + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { + DestroyWindow(hDlg); + return FALSE; + } + + SetDlgItemText(hDlg, IDC_BTNAPP, TranslateT("Run application")); + SetDlgItemText(hDlg, IDC_BTNDEL, TranslateT("Delete selected")); + SetDlgItemText(hDlg, IDC_BTNCHECKALL, TranslateT("Select All")); + SetDlgItemText(hDlg, IDC_BTNOK, TranslateT("OK")); + + LVCOLUMN lvc0 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, FromWidth, TranslateT("From"), 0, 0}; + LVCOLUMN lvc1 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SubjectWidth, TranslateT("Subject"), 0, 0}; + LVCOLUMN lvc2 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SizeWidth, TranslateT("Size"), 0, 0}; + LVCOLUMN lvc3 = {LVCF_FMT | LVCF_TEXT | LVCF_WIDTH, LVCFMT_LEFT, SizeDate, TranslateT("Date"), 0, 0}; + SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc0); + SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc1); + SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, (WPARAM)2, (LPARAM)&lvc2); + SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_INSERTCOLUMN, (WPARAM)3, (LPARAM)&lvc3); + + if ((ActualAccount->NewMailN.App != nullptr) && (mir_wstrlen(ActualAccount->NewMailN.App))) + EnableWindow(GetDlgItem(hDlg, IDC_BTNAPP), TRUE); + else + EnableWindow(GetDlgItem(hDlg, IDC_BTNAPP), FALSE); + + ReadDoneFcn(ActualAccount->AccountAccessSO); + + WindowList_Add(YAMNVar.MessageWnds, hDlg); + WindowList_Add(YAMNVar.NewMailAccountWnd, hDlg, (UINT_PTR)ActualAccount); + + { + wchar_t accstatus[512]; + GetStatusFcn(ActualAccount, accstatus); + SetDlgItemText(hDlg, IDC_STSTATUS, accstatus); + } + SetTimer(hDlg, TIMER_FLASHING, 500, nullptr); + + if (ActualAccount->hContact != NULL) + g_clistApi.pfnRemoveEvent(ActualAccount->hContact, (LPARAM)"yamn new mail message"); + + mir_subclassWindow(GetDlgItem(hDlg, IDC_LISTMAILS), ListViewSubclassProc); + } + break; + + case WM_DESTROY: + { + RECT coord; + LVCOLUMN ColInfo; + HYAMNMAIL Parser; + + Window_FreeIcon_IcoLib(hDlg); + + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + ColInfo.mask = LVCF_WIDTH; + if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 0, &ColInfo)) + FromWidth = ColInfo.cx; + if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 1, &ColInfo)) + SubjectWidth = ColInfo.cx; + if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 2, &ColInfo)) + SizeWidth = ColInfo.cx; + if (ListView_GetColumn(GetDlgItem(hDlg, IDC_LISTMAILS), 3, &ColInfo)) + SizeDate = ColInfo.cx; + + if (!YAMNVar.Shutdown && GetWindowRect(hDlg, &coord)) //the YAMNVar.Shutdown testing is because MMessagesAccessSO)) + break; + + //delete mails from queue, which are deleted from server (spam level 3 mails e.g.) + for (Parser = (HYAMNMAIL)ActualAccount->Mails; Parser != nullptr; Parser = Parser->Next) { + if ((Parser->Flags & YAMN_MSG_DELETED) && YAMN_MSG_SPAML(Parser->Flags, YAMN_MSG_SPAML3) && mwui->Seen) //if spaml3 was already deleted and user knows about it + { + DeleteMessageFromQueueFcn((HYAMNMAIL *)&ActualAccount->Mails, Parser, 1); + CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)ActualAccount->Plugin, (LPARAM)Parser); + } + } + + //mark mails as read (remove "new" and "unseen" flags) + if (mwui->Seen) + SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY, 0, YAMN_MSG_NEW | YAMN_MSG_UNSEEN, 0); + + WriteDoneFcn(ActualAccount->MessagesAccessSO); + + NOTIFYICONDATA nid; + memset(&nid, 0, sizeof(NOTIFYICONDATA)); + + delete mwui; + SetWindowLongPtr(hDlg, DWLP_USER, NULL); + + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hDlg; + nid.uID = 0; + Shell_NotifyIcon(NIM_DELETE, &nid); + PostQuitMessage(0); + } + break; + + case WM_SHOWWINDOW: + { + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + + if (mwui == nullptr) + return 0; + mwui->Seen = TRUE; + } + + case WM_YAMN_CHANGESTATUS: + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + + if ((CAccount *)wParam != ActualAccount) + break; + + wchar_t accstatus[512]; + GetStatusFcn(ActualAccount, accstatus); + SetDlgItemText(hDlg, IDC_STSTATUS, accstatus); + return 1; + + case WM_YAMN_CHANGECONTENT: + { + struct CUpdateMails UpdateParams; + BOOL ThisThreadWindow = (GetCurrentThreadId() == GetWindowThreadProcessId(hDlg, nullptr)); + + if (nullptr == (UpdateParams.Copied = CreateEvent(nullptr, FALSE, FALSE, nullptr))) { + DestroyWindow(hDlg); + return 0; + } + UpdateParams.Flags = (struct CChangeContent *)lParam; + UpdateParams.Waiting = !ThisThreadWindow; + + if (ThisThreadWindow) { + if (!UpdateMails(hDlg, (CAccount *)wParam, UpdateParams.Flags->nflags, UpdateParams.Flags->nnflags)) + DestroyWindow(hDlg); + } + else if (PostMessage(hDlg, WM_YAMN_UPDATEMAILS, wParam, (LPARAM)&UpdateParams)) //this ensures UpdateMails will execute the thread who created the browser window + { + if (!ThisThreadWindow) + WaitForSingleObject(UpdateParams.Copied, INFINITE); + } + + CloseHandle(UpdateParams.Copied); + } + return 1; + case WM_YAMN_UPDATEMAILS: + { + struct CUpdateMails *um = (struct CUpdateMails *)lParam; + uint32_t nflags, nnflags; + + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + return 0; + if ((CAccount *)wParam != ActualAccount) + return 0; + + nflags = um->Flags->nflags; + nnflags = um->Flags->nnflags; + + if (um->Waiting) + SetEvent(um->Copied); + + if (!UpdateMails(hDlg, ActualAccount, nflags, nnflags)) + DestroyWindow(hDlg); + } + return 1; + case WM_YAMN_STOPACCOUNT: + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + if ((CAccount *)wParam != ActualAccount) + break; + PostQuitMessage(0); + return 1; + + case WM_YAMN_NOTIFYICON: + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + + switch (lParam) { + case WM_LBUTTONDBLCLK: + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { + return 0; + } + + if (ActualAccount->AbilityFlags & YAMN_ACC_BROWSE) { + ShowWindow(hDlg, SW_SHOWNORMAL); + SetForegroundWindow(hDlg); + } + else DestroyWindow(hDlg); + + ReadDoneFcn(ActualAccount->AccountAccessSO); + break; + } + break; + + case WM_YAMN_SHOWSELECTED: + { + int iSelect = SendDlgItemMessage(hDlg, IDC_LISTMAILS, LVM_GETNEXTITEM, -1, MAKELPARAM((UINT)LVNI_FOCUSED, 0)); // return item selected + if (iSelect != -1) { + LV_ITEMW item; + + item.iItem = iSelect; + item.iSubItem = 0; + item.mask = LVIF_PARAM | LVIF_STATE; + item.stateMask = 0xFFFFFFFF; + ListView_GetItem(GetDlgItem(hDlg, IDC_LISTMAILS), &item); + HYAMNMAIL ActualMail = (HYAMNMAIL)item.lParam; + if (nullptr != ActualMail) { + PYAMN_MAILSHOWPARAM MailParam = new YAMN_MAILSHOWPARAM; + MailParam->account = GetWindowAccount(hDlg); + MailParam->mail = ActualMail; + mir_forkthread(ShowEmailThread, MailParam); + } + } + } + break; + + case WM_SYSCOMMAND: + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + switch (wParam) { + case SC_CLOSE: + DestroyWindow(hDlg); + break; + } + break; + + case WM_COMMAND: + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + + switch (LOWORD(wParam)) { + case IDC_BTNCHECKALL: + ListView_SetItemState(GetDlgItem(hDlg, IDC_LISTMAILS), -1, 0, LVIS_SELECTED); // deselect all items + ListView_SetItemState(GetDlgItem(hDlg, IDC_LISTMAILS), -1, LVIS_SELECTED, LVIS_SELECTED); + Items = ListView_GetItemCount(GetDlgItem(hDlg, IDC_LISTMAILS)); + ListView_RedrawItems(GetDlgItem(hDlg, IDC_LISTMAILS), 0, Items); + UpdateWindow(GetDlgItem(hDlg, IDC_LISTMAILS)); + SetFocus(GetDlgItem(hDlg, IDC_LISTMAILS)); + break; + + case IDC_BTNOK: + DestroyWindow(hDlg); + break; + + case IDC_BTNAPP: + { + PROCESS_INFORMATION pi; + STARTUPINFOW si; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + if (ActualAccount->NewMailN.App != nullptr) { + wchar_t *Command; + if (ActualAccount->NewMailN.AppParam != nullptr) + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; + else + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; + + if (Command != nullptr) { + mir_wstrcpy(Command, L"\""); + mir_wstrcat(Command, ActualAccount->NewMailN.App); + mir_wstrcat(Command, L"\" "); + if (ActualAccount->NewMailN.AppParam != nullptr) + mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); + CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + delete[] Command; + } + } + + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + + if (!(GetKeyState(VK_SHIFT) & 0x8000) && !(GetKeyState(VK_CONTROL) & 0x8000)) + DestroyWindow(hDlg); + } + break; + + case IDC_BTNDEL: + { + HYAMNMAIL ActualMail; + uint32_t Total = 0; + + // we use event to signal, that running thread has all needed stack parameters copied + HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (ThreadRunningEV == nullptr) + break; + + Items = ListView_GetItemCount(GetDlgItem(hDlg, IDC_LISTMAILS)); + + LVITEM item; + item.stateMask = 0xFFFFFFFF; + + if (WAIT_OBJECT_0 == WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { + for (int i = 0; i < Items; i++) { + item.iItem = i; + item.iSubItem = 0; + item.mask = LVIF_PARAM | LVIF_STATE; + item.stateMask = 0xFFFFFFFF; + ListView_GetItem(GetDlgItem(hDlg, IDC_LISTMAILS), &item); + ActualMail = (HYAMNMAIL)item.lParam; + if (nullptr == ActualMail) + break; + if (item.state & LVIS_SELECTED) { + ActualMail->Flags |= YAMN_MSG_USERDELETE; //set to mail we are going to delete it + Total++; + } + } + + // Enable write-access to mails + WriteDoneFcn(ActualAccount->MessagesAccessSO); + + if (Total) { + wchar_t DeleteMsg[1024]; + + mir_snwprintf(DeleteMsg, TranslateT("Do you really want to delete %d selected mails?"), Total); + if (IDOK == MessageBox(hDlg, DeleteMsg, TranslateT("Delete confirmation"), MB_OKCANCEL | MB_ICONWARNING)) { + struct DeleteParam ParamToDeleteMails = {YAMN_DELETEVERSION, ThreadRunningEV, ActualAccount, nullptr}; + + // Find if there's mail marked to delete, which was deleted before + if (WAIT_OBJECT_0 == WaitToWriteFcn(ActualAccount->MessagesAccessSO)) { + for (ActualMail = (HYAMNMAIL)ActualAccount->Mails; ActualMail != nullptr; ActualMail = ActualMail->Next) { + if ((ActualMail->Flags & YAMN_MSG_DELETED) && ((ActualMail->Flags & YAMN_MSG_USERDELETE))) //if selected mail was already deleted + { + DeleteMessageFromQueueFcn((HYAMNMAIL *)&ActualAccount->Mails, ActualMail, 1); + CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)ActualAccount->Plugin, (LPARAM)ActualMail); //delete it from memory + continue; + } + } + // Set flag to marked mails that they can be deleted + SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY | YAMN_MSG_USERDELETE, 0, YAMN_MSG_DELETEOK, 1); + // Create new thread which deletes marked mails. + HANDLE NewThread = mir_forkthread(ActualAccount->Plugin->Fcn->DeleteMailsFcnPtr, &ParamToDeleteMails); + if (NewThread != nullptr) + WaitForSingleObject(ThreadRunningEV, INFINITE); + + // Enable write-access to mails + WriteDoneFcn(ActualAccount->MessagesAccessSO); + } + } + else //else mark messages that they are not to be deleted + SetRemoveFlagsInQueueFcn((HYAMNMAIL)ActualAccount->Mails, YAMN_MSG_DISPLAY | YAMN_MSG_USERDELETE, 0, YAMN_MSG_USERDELETE, 0); + } + } + CloseHandle(ThreadRunningEV); + if (g_plugin.getByte(YAMN_CLOSEDELETE, 0)) + DestroyWindow(hDlg); + } + break; + } + break; + + case WM_SIZE: + if (wParam == SIZE_RESTORED) { + LONG x = LOWORD(lParam); //((LPRECT)lParam)->right-((LPRECT)lParam)->left; + LONG y = HIWORD(lParam); //((LPRECT)lParam)->bottom-((LPRECT)lParam)->top; + MoveWindow(GetDlgItem(hDlg, IDC_BTNDEL), 5, y - 5 - 25, (x - 20) / 3, 25, TRUE); //where to put DELETE button while resizing + MoveWindow(GetDlgItem(hDlg, IDC_BTNCHECKALL), 10 + (x - 20) / 3, y - 5 - 25, (x - 20) / 6, 25, TRUE); //where to put CHECK ALL button while resizing + MoveWindow(GetDlgItem(hDlg, IDC_BTNAPP), 15 + (x - 20) / 3 + (x - 20) / 6, y - 5 - 25, (x - 20) / 3, 25, TRUE); //where to put RUN APP button while resizing + MoveWindow(GetDlgItem(hDlg, IDC_BTNOK), 20 + 2 * (x - 20) / 3 + (x - 20) / 6, y - 5 - 25, (x - 20) / 6, 25, TRUE); //where to put OK button while resizing + MoveWindow(GetDlgItem(hDlg, IDC_LISTMAILS), 5, 5, x - 10, y - 55, TRUE); //where to put list mail window while resizing + MoveWindow(GetDlgItem(hDlg, IDC_STSTATUS), 5, y - 5 - 45, x - 10, 15, TRUE); //where to put account status text while resizing + } + return 0; + + case WM_GETMINMAXINFO: + ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = MAILBROWSER_MINXSIZE; + ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = MAILBROWSER_MINYSIZE; + return 0; + + case WM_TIMER: + { + NOTIFYICONDATA nid; + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hDlg; + nid.uID = 0; + nid.uFlags = NIF_ICON; + if (mwui->TrayIconState == 0) + nid.hIcon = g_plugin.getIcon(IDI_CHECKMAIL); + else + nid.hIcon = g_plugin.getIcon(IDI_NEWMAIL); + Shell_NotifyIcon(NIM_MODIFY, &nid); + mwui->TrayIconState = !mwui->TrayIconState; + // UpdateWindow(hDlg); + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case IDC_LISTMAILS: + switch (((LPNMHDR)lParam)->code) { + case NM_DBLCLK: + SendMessage(hDlg, WM_YAMN_SHOWSELECTED, 0, 0); + break; + + case LVN_COLUMNCLICK: + if (nullptr != (ActualAccount = GetWindowAccount(hDlg))) { + NM_LISTVIEW *pNMListView = (NM_LISTVIEW *)lParam; + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + switch ((int)pNMListView->iSubItem) { + case 0: + bFrom = !bFrom; + break; + case 1: + bSub = !bSub; + break; + case 2: + bSize = !bSize; + break; + case 3: + bDate = !bDate; + break; + default: + break; + } + ListView_SortItems(pNMListView->hdr.hwndFrom, ListViewCompareProc, pNMListView->iSubItem); + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + } + break; + + case NM_CUSTOMDRAW: + { + LPNMLVCUSTOMDRAW cd = (LPNMLVCUSTOMDRAW)lParam; + LONG_PTR PaintCode; + + if (nullptr == (ActualAccount = GetWindowAccount(hDlg))) + break; + + switch (cd->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + PaintCode = CDRF_NOTIFYITEMDRAW; + break; + case CDDS_ITEMPREPAINT: + PaintCode = CDRF_NOTIFYSUBITEMDRAW; + break; + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: + { + BOOL umma; + { + struct CMailWinUserInfo *mwui = (struct CMailWinUserInfo *)GetWindowLongPtr(hDlg, DWLP_USER); + umma = mwui->UpdateMailsMessagesAccess; + } + HYAMNMAIL ActualMail = (HYAMNMAIL)cd->nmcd.lItemlParam; + if (!ActualMail) + ActualMail = (HYAMNMAIL)readItemLParam(cd->nmcd.hdr.hwndFrom, cd->nmcd.dwItemSpec); + + if (!umma) + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->MessagesAccessSO)) + return 0; + + switch (ActualMail->Flags & YAMN_MSG_SPAMMASK) { + case YAMN_MSG_SPAML1: + case YAMN_MSG_SPAML2: + cd->clrText = RGB(150, 150, 150); + break; + case YAMN_MSG_SPAML3: + cd->clrText = RGB(200, 200, 200); + cd->clrTextBk = RGB(160, 160, 160); + break; + case 0: + if (cd->nmcd.dwItemSpec & 1) + cd->clrTextBk = RGB(230, 230, 230); + break; + default: + break; + } + if (ActualMail->Flags & YAMN_MSG_UNSEEN) + cd->clrTextBk = RGB(220, 235, 250); + PaintCode = CDRF_DODEFAULT; + + if (!umma) + ReadDoneFcn(ActualAccount->MessagesAccessSO); + break; + } + default: + PaintCode = 0; + } + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PaintCode); + return 1; + } + } + } + break; + + case WM_CONTEXTMENU: + if (GetWindowLongPtr((HWND)wParam, GWLP_ID) == IDC_LISTMAILS) { + //MessageBox(0,"LISTHEADERS","Debug",0); + HWND hList = GetDlgItem(hDlg, IDC_LISTMAILS); + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + if (pt.x == -1) pt.x = 0; + if (pt.y == -1) pt.y = 0; + if (int numRows = ListView_GetItemCount(hList)) { + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)1, TranslateT("Copy Selected")); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)2, TranslateT("Copy All")); + AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)0, TranslateT("Cancel")); + int nReturnCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hDlg, nullptr); + DestroyMenu(hMenu); + if (nReturnCmd > 0) { + int courRow = 0; + size_t sizeNeeded = 0; + wchar_t from[128] = {0}, subject[256] = {0}, size[16] = {0}, date[64] = {0}; + for (courRow = 0; courRow < numRows; courRow++) { + if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; + ListView_GetItemText(hList, courRow, 0, from, _countof(from)); + ListView_GetItemText(hList, courRow, 1, subject, _countof(subject)); + ListView_GetItemText(hList, courRow, 2, size, _countof(size)); + ListView_GetItemText(hList, courRow, 3, date, _countof(date)); + sizeNeeded += 5 + mir_wstrlen(from) + mir_wstrlen(subject) + mir_wstrlen(size) + mir_wstrlen(date); + } + if (sizeNeeded && OpenClipboard(hDlg)) { + EmptyClipboard(); + HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE, (sizeNeeded + 1) * sizeof(wchar_t)); + wchar_t *buff = (wchar_t *)GlobalLock(hData); + int courPos = 0; + for (courRow = 0; courRow < numRows; courRow++) { + if ((nReturnCmd == 1) && (ListView_GetItemState(hList, courRow, LVIS_SELECTED) == 0)) continue; + ListView_GetItemText(hList, courRow, 0, from, _countof(from)); + ListView_GetItemText(hList, courRow, 1, subject, _countof(subject)); + ListView_GetItemText(hList, courRow, 2, size, _countof(size)); + ListView_GetItemText(hList, courRow, 3, date, _countof(date)); + courPos += mir_snwprintf(&buff[courPos], sizeNeeded + 1, L"%s\t%s\t%s\t%s\r\n", from, subject, size, date); + } + GlobalUnlock(hData); + + SetClipboardData(CF_UNICODETEXT, hData); + + CloseClipboard(); + } + } + } + } + break; // just in case + } + return 0; +} + +LRESULT CALLBACK ListViewSubclassProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hwndParent = GetParent(hDlg); + + switch (msg) { + case WM_GETDLGCODE: + { + LPMSG lpmsg = (LPMSG)lParam; + if (lpmsg != nullptr) { + if (lpmsg->message == WM_KEYDOWN + && lpmsg->wParam == VK_RETURN) + return DLGC_WANTALLKEYS; + } + } + break; + + case WM_KEYDOWN: + { + BOOL isCtrl = GetKeyState(VK_CONTROL) & 0x8000; + BOOL isShift = GetKeyState(VK_SHIFT) & 0x8000; + BOOL isAlt = GetKeyState(VK_MENU) & 0x8000; + + switch (wParam) { + case 'A': // ctrl-a + if (!isAlt && !isShift && isCtrl) SendMessage(hwndParent, WM_COMMAND, IDC_BTNCHECKALL, 0); + break; + case VK_RETURN: + case VK_SPACE: + if (!isAlt && !isShift && !isCtrl) SendMessage(hwndParent, WM_YAMN_SHOWSELECTED, 0, 0); + break; + case VK_DELETE: + SendMessage(hwndParent, WM_COMMAND, IDC_BTNDEL, 0); + break; + } + } + break; + } + return mir_callNextSubclass(hDlg, ListViewSubclassProc, msg, wParam, lParam); +} + +void __cdecl MailBrowser(void *Param) +{ + MSG msg; + + HWND hMailBrowser; + BOOL WndFound = FALSE; + + struct MailBrowserWinParam MyParam = *(struct MailBrowserWinParam *)Param; + CAccount *ActualAccount = MyParam.account; + SCIncFcn(ActualAccount->UsingThreads); + + // we will not use params in stack anymore + SetEvent(MyParam.ThreadRunningEV); + + __try { + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) + return; + + if (!(ActualAccount->AbilityFlags & YAMN_ACC_BROWSE)) { + MyParam.nflags = MyParam.nflags & ~YAMN_ACC_MSG; + MyParam.nnflags = MyParam.nnflags & ~YAMN_ACC_MSG; + } + + if (!(ActualAccount->AbilityFlags & YAMN_ACC_POPUP)) + MyParam.nflags = MyParam.nflags & ~YAMN_ACC_POP; + + ReadDoneFcn(ActualAccount->AccountAccessSO); + + if (nullptr != (hMailBrowser = WindowList_Find(YAMNVar.NewMailAccountWnd, (UINT_PTR)ActualAccount))) + WndFound = TRUE; + + if ((hMailBrowser == nullptr) && ((MyParam.nflags & YAMN_ACC_MSG) || (MyParam.nflags & YAMN_ACC_ICO) || (MyParam.nnflags & YAMN_ACC_MSG))) { + hMailBrowser = CreateDialogParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_DLGVIEWMESSAGES), nullptr, DlgProcYAMNMailBrowser, (LPARAM)&MyParam); + Window_SetIcon_IcoLib(hMailBrowser, g_plugin.getIconHandle(IDI_NEWMAIL)); + MoveWindow(hMailBrowser, PosX, PosY, SizeX, SizeY, TRUE); + } + + if (hMailBrowser != nullptr) { + struct CChangeContent Params = {MyParam.nflags, MyParam.nnflags}; //if this thread created window, just post message to update mails + + SendMessage(hMailBrowser, WM_YAMN_CHANGECONTENT, (WPARAM)ActualAccount, (LPARAM)&Params); //we ensure this will do the thread who created the browser window + } + else + UpdateMails(nullptr, ActualAccount, MyParam.nflags, MyParam.nnflags); //update mails without displaying or refreshing any window + + if ((hMailBrowser != nullptr) && !WndFound) { //we process message loop only for thread that created window + while (GetMessage(&msg, nullptr, 0, 0)) { + if (hMailBrowser == nullptr || !IsDialogMessage(hMailBrowser, &msg)) { /* Wine fix. */ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + if ((!WndFound) && (ActualAccount->Plugin->Fcn != nullptr) && (ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr != nullptr) && ActualAccount->AbleToWork) + ActualAccount->Plugin->Fcn->WriteAccountsFcnPtr(); + } + __finally { + SCDecFcn(ActualAccount->UsingThreads); + } +} + +INT_PTR RunMailBrowserSvc(WPARAM wParam, LPARAM lParam) +{ + PYAMN_MAILBROWSERPARAM Param = (PYAMN_MAILBROWSERPARAM)wParam; + + if ((uint32_t)lParam != YAMN_MAILBROWSERVERSION) + return 0; + + //an event for successfull copy parameters to which point a pointer in stack for new thread + HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); + Param->ThreadRunningEV = ThreadRunningEV; + + HANDLE NewThread = mir_forkthread(MailBrowser, Param); + if (NewThread != nullptr) + WaitForSingleObject(ThreadRunningEV, INFINITE); + + CloseHandle(ThreadRunningEV); + return 1; +} diff --git a/protocols/YAMN/src/debug.cpp b/protocols/YAMN/src/debug.cpp index 1fc41adc19..65322366ab 100644 --- a/protocols/YAMN/src/debug.cpp +++ b/protocols/YAMN/src/debug.cpp @@ -1,111 +1,111 @@ -/* - * YAMN plugin main file - * Miranda homepage: http://miranda-icq.sourceforge.net/ - * - * Debug functions used in DEBUG release (you need to global #define DEBUG to get debug version) - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - -#ifdef _DEBUG - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -wchar_t DebugUserDirectory[MAX_PATH] = L"."; -CRITICAL_SECTION FileAccessCS; - -#ifdef DEBUG_COMM -wchar_t DebugCommFileName2[]=L"%s\\yamn-debug.comm.log"; -HANDLE CommFile; -#endif - -#ifdef DEBUG_DECODE -wchar_t DebugDecodeFileName2[]=L"%s\\yamn-debug.decode.log"; -HANDLE DecodeFile; -#endif - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -void InitDebug() -{ -#if defined (DEBUG_COMM) || defined (DEBUG_DECODE) - wchar_t DebugFileName[MAX_PATH]; -#endif - InitializeCriticalSection(&FileAccessCS); - -#ifdef DEBUG_COMM - mir_snwprintf(DebugFileName, DebugCommFileName2, DebugUserDirectory); - - CommFile=CreateFile(DebugFileName,GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL); - DebugLog(CommFile,"Communication debug file created by %s\n",YAMN_VER); -#endif - -#ifdef DEBUG_DECODE - mir_snwprintf(DebugFileName, DebugDecodeFileName2, DebugUserDirectory); - - DecodeFile=CreateFile(DebugFileName,GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL); - DebugLog(DecodeFile,"Decoding kernel debug file created by %s\n",YAMN_VER); -#endif -} - -void UnInitDebug() -{ - DeleteCriticalSection(&FileAccessCS); -#ifdef DEBUG_COMM - DebugLog(CommFile,"File is being closed normally."); - CloseHandle(CommFile); -#endif -#ifdef DEBUG_DECODE - DebugLog(DecodeFile,"File is being closed normally."); - CloseHandle(DecodeFile); -#endif -} - - -void DebugLog(HANDLE File,const char *fmt,...) -{ - char *str; - char tids[32]; - va_list vararg; - int strsize; - DWORD Written; - - va_start(vararg,fmt); - str=(char *)malloc(strsize=65536); - mir_snprintf(tids, "[%x]",GetCurrentThreadId()); - while(mir_vsnprintf(str, strsize, fmt, vararg)==-1) - str=(char *)realloc(str,strsize+=65536); - va_end(vararg); - EnterCriticalSection(&FileAccessCS); - WriteFile(File,tids,(uint32_t)mir_strlen(tids),&Written,nullptr); - WriteFile(File,str,(uint32_t)mir_strlen(str),&Written,nullptr); - LeaveCriticalSection(&FileAccessCS); - free(str); -} - -void DebugLogW(HANDLE File,const wchar_t *fmt,...) -{ - wchar_t *str; - char tids[32]; - va_list vararg; - int strsize; - DWORD Written; - - va_start(vararg,fmt); - str=(wchar_t *)malloc((strsize=65536)*sizeof(wchar_t)); - mir_snprintf(tids, "[%x]",GetCurrentThreadId()); - while(mir_vsnwprintf(str, strsize, fmt, vararg)==-1) - str=(wchar_t *)realloc(str,(strsize+=65536)*sizeof(wchar_t)); - va_end(vararg); - EnterCriticalSection(&FileAccessCS); - WriteFile(File,tids,(uint32_t)mir_strlen(tids),&Written,nullptr); - WriteFile(File,str,(uint32_t)mir_wstrlen(str)*sizeof(wchar_t),&Written,nullptr); - LeaveCriticalSection(&FileAccessCS); - free(str); -} - +/* + * YAMN plugin main file + * Miranda homepage: http://miranda-icq.sourceforge.net/ + * + * Debug functions used in DEBUG release (you need to global #define DEBUG to get debug version) + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + +#ifdef _DEBUG + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +wchar_t DebugUserDirectory[MAX_PATH] = L"."; +CRITICAL_SECTION FileAccessCS; + +#ifdef DEBUG_COMM +wchar_t DebugCommFileName2[]=L"%s\\yamn-debug.comm.log"; +HANDLE CommFile; +#endif + +#ifdef DEBUG_DECODE +wchar_t DebugDecodeFileName2[]=L"%s\\yamn-debug.decode.log"; +HANDLE DecodeFile; +#endif + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +void InitDebug() +{ +#if defined (DEBUG_COMM) || defined (DEBUG_DECODE) + wchar_t DebugFileName[MAX_PATH]; +#endif + InitializeCriticalSection(&FileAccessCS); + +#ifdef DEBUG_COMM + mir_snwprintf(DebugFileName, DebugCommFileName2, DebugUserDirectory); + + CommFile=CreateFile(DebugFileName,GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL); + DebugLog(CommFile,"Communication debug file created by %s\n",YAMN_VER); +#endif + +#ifdef DEBUG_DECODE + mir_snwprintf(DebugFileName, DebugDecodeFileName2, DebugUserDirectory); + + DecodeFile=CreateFile(DebugFileName,GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL); + DebugLog(DecodeFile,"Decoding kernel debug file created by %s\n",YAMN_VER); +#endif +} + +void UnInitDebug() +{ + DeleteCriticalSection(&FileAccessCS); +#ifdef DEBUG_COMM + DebugLog(CommFile,"File is being closed normally."); + CloseHandle(CommFile); +#endif +#ifdef DEBUG_DECODE + DebugLog(DecodeFile,"File is being closed normally."); + CloseHandle(DecodeFile); +#endif +} + + +void DebugLog(HANDLE File,const char *fmt,...) +{ + char *str; + char tids[32]; + va_list vararg; + int strsize; + DWORD Written; + + va_start(vararg,fmt); + str=(char *)malloc(strsize=65536); + mir_snprintf(tids, "[%x]",GetCurrentThreadId()); + while(mir_vsnprintf(str, strsize, fmt, vararg)==-1) + str=(char *)realloc(str,strsize+=65536); + va_end(vararg); + EnterCriticalSection(&FileAccessCS); + WriteFile(File,tids,(uint32_t)mir_strlen(tids),&Written,nullptr); + WriteFile(File,str,(uint32_t)mir_strlen(str),&Written,nullptr); + LeaveCriticalSection(&FileAccessCS); + free(str); +} + +void DebugLogW(HANDLE File,const wchar_t *fmt,...) +{ + wchar_t *str; + char tids[32]; + va_list vararg; + int strsize; + DWORD Written; + + va_start(vararg,fmt); + str=(wchar_t *)malloc((strsize=65536)*sizeof(wchar_t)); + mir_snprintf(tids, "[%x]",GetCurrentThreadId()); + while(mir_vsnwprintf(str, strsize, fmt, vararg)==-1) + str=(wchar_t *)realloc(str,(strsize+=65536)*sizeof(wchar_t)); + va_end(vararg); + EnterCriticalSection(&FileAccessCS); + WriteFile(File,tids,(uint32_t)mir_strlen(tids),&Written,nullptr); + WriteFile(File,str,(uint32_t)mir_wstrlen(str)*sizeof(wchar_t),&Written,nullptr); + LeaveCriticalSection(&FileAccessCS); + free(str); +} + #endif //ifdef DEBUG \ No newline at end of file diff --git a/protocols/YAMN/src/debug.h b/protocols/YAMN/src/debug.h index a13ac952b0..e2e8e3e506 100644 --- a/protocols/YAMN/src/debug.h +++ b/protocols/YAMN/src/debug.h @@ -1,50 +1,50 @@ -#ifndef __DEBUG_H -#define __DEBUG_H - -#ifdef _DEBUG - -//#define DEBUG_COMM //debug communiation to a file -//#define DEBUG_DECODE //debug header decoding to a file -//#define DEBUG_DECODECODEPAGE //add info about codepage used in conversion -//#define DEBUG_DECODEBASE64 //add info about base64 result -//#define DEBUG_DECODEQUOTED //add info about quoted printable result -//#define DEBUG_FILEREAD //debug file reading to message boxes -//#define DEBUG_FILEREADMESSAGES //debug file reading messages to message boxes - -void DebugLog(HANDLE,const char *fmt,...); -void DebugLogW(HANDLE File,const wchar_t *fmt,...); - -#ifdef DEBUG_SYNCHRO -// Used for synchronization debug -extern HANDLE SynchroFile; -#endif - -#ifdef DEBUG_COMM -// Used for communication debug -extern HANDLE CommFile; -#endif - -#ifdef DEBUG_DECODE -// Used for decoding debug -extern HANDLE DecodeFile; -#endif - -#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) -uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo,char *DebugString); - -uint32_t ReadStringFromMemoryW(char **Parser,wchar_t *End,char **StoreTo,wchar_t *DebugString); - -#else -uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo); - -uint32_t ReadStringFromMemoryW(wchar_t **Parser,wchar_t *End,wchar_t **StoreTo); - -#endif - -//#ifdef DEBUG_ACCOUNTS -//int GetAccounts(); -//void WriteAccounts(); -//#endif - -#endif //YAMN_DEBUG -#endif //_DEBUG_H +#ifndef __DEBUG_H +#define __DEBUG_H + +#ifdef _DEBUG + +//#define DEBUG_COMM //debug communiation to a file +//#define DEBUG_DECODE //debug header decoding to a file +//#define DEBUG_DECODECODEPAGE //add info about codepage used in conversion +//#define DEBUG_DECODEBASE64 //add info about base64 result +//#define DEBUG_DECODEQUOTED //add info about quoted printable result +//#define DEBUG_FILEREAD //debug file reading to message boxes +//#define DEBUG_FILEREADMESSAGES //debug file reading messages to message boxes + +void DebugLog(HANDLE,const char *fmt,...); +void DebugLogW(HANDLE File,const wchar_t *fmt,...); + +#ifdef DEBUG_SYNCHRO +// Used for synchronization debug +extern HANDLE SynchroFile; +#endif + +#ifdef DEBUG_COMM +// Used for communication debug +extern HANDLE CommFile; +#endif + +#ifdef DEBUG_DECODE +// Used for decoding debug +extern HANDLE DecodeFile; +#endif + +#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) +uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo,char *DebugString); + +uint32_t ReadStringFromMemoryW(char **Parser,wchar_t *End,char **StoreTo,wchar_t *DebugString); + +#else +uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo); + +uint32_t ReadStringFromMemoryW(wchar_t **Parser,wchar_t *End,wchar_t **StoreTo); + +#endif + +//#ifdef DEBUG_ACCOUNTS +//int GetAccounts(); +//void WriteAccounts(); +//#endif + +#endif //YAMN_DEBUG +#endif //_DEBUG_H diff --git a/protocols/YAMN/src/filterplugin.cpp b/protocols/YAMN/src/filterplugin.cpp index 1d55f3a9c1..451ea155aa 100644 --- a/protocols/YAMN/src/filterplugin.cpp +++ b/protocols/YAMN/src/filterplugin.cpp @@ -1,169 +1,169 @@ -/* - * YAMN plugin export functions for filtering - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - - //-------------------------------------------------------------------------------------------------- - //-------------------------------------------------------------------------------------------------- - -PYAMN_FILTERPLUGINQUEUE FirstFilterPlugin = nullptr; - -INT_PTR RegisterFilterPluginSvc(WPARAM, LPARAM); - -//Removes plugin from queue and deletes its structures -INT_PTR UnregisterFilterPlugin(HYAMNFILTERPLUGIN Plugin); - -INT_PTR UnregisterFilterPluginSvc(WPARAM wParam, LPARAM lParam); - -//Removes all filter plugins -INT_PTR UnregisterFilterPlugins(); - -INT_PTR FilterMailSvc(WPARAM, LPARAM); - -//Sets imported functions for an plugin and therefore it starts plugin to be registered and running -// Plugin- plugin, which wants to set its functions -// Importance- importance of plugin (see m_filterplugin.h) -// YAMNFilterFcn- pointer to imported functions -// YAMNfilterFcnVer- version of YAMN_FILTERIMPORTFCN, use YAMN_FILTERIMPORTFCNVERSION -// returns nonzero if success -int WINAPI SetFilterPluginFcnImportFcn(HYAMNFILTERPLUGIN Plugin, uint32_t Importance, PYAMN_FILTERIMPORTFCN YAMNFilterFcn, uint32_t YAMNFilterFcnVer); - -struct CExportedFunctions FilterPluginExportedFcn[] = -{ - {YAMN_SETFILTERPLUGINFCNIMPORTID, (void *)SetFilterPluginFcnImportFcn}, -}; - -struct CExportedServices FilterPluginExportedSvc[] = -{ - {MS_YAMN_REGISTERFILTERPLUGIN, RegisterFilterPluginSvc}, - {MS_YAMN_UNREGISTERFILTERPLUGIN, UnregisterFilterPluginSvc}, -}; - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -INT_PTR RegisterFilterPluginSvc(WPARAM wParam, LPARAM lParam) -{ - PYAMN_FILTERREGISTRATION Registration = (PYAMN_FILTERREGISTRATION)wParam; - HYAMNFILTERPLUGIN Plugin; - - if (lParam != YAMN_FILTERREGISTRATIONVERSION) - return 0; - if ((Registration->Name == nullptr) || (Registration->Ver == nullptr)) - return NULL; - if (nullptr == (Plugin = new YAMN_FILTERPLUGIN)) - return NULL; - - Plugin->PluginInfo = Registration; - Plugin->FilterFcn = nullptr; - return (INT_PTR)Plugin; -} - -INT_PTR UnregisterFilterPlugin(HYAMNFILTERPLUGIN Plugin) -{ - PYAMN_FILTERPLUGINQUEUE Parser, Found; - - if (FirstFilterPlugin->Plugin == Plugin) { - Found = FirstFilterPlugin; - FirstFilterPlugin = FirstFilterPlugin->Next; - } - else { - for (Parser = FirstFilterPlugin; (Parser->Next != nullptr) && (Plugin != Parser->Next->Plugin); Parser = Parser->Next); - if (Parser->Next != nullptr) { - Found = Parser->Next; - Parser->Next = Parser->Next->Next; - } - else - Found = nullptr; - } - if (Found != nullptr) { - if (Plugin->FilterFcn->UnLoadFcn != nullptr) - Plugin->FilterFcn->UnLoadFcn((void *)nullptr); - - delete Found->Plugin; - delete Found; - return 1; - } - - return 0; -} - -INT_PTR UnregisterFilterPluginSvc(WPARAM wParam, LPARAM) -{ - HYAMNFILTERPLUGIN Plugin = (HYAMNFILTERPLUGIN)wParam; - - mir_cslock lck(PluginRegCS); - UnregisterFilterPlugin(Plugin); - return 1; -} - -INT_PTR UnregisterFilterPlugins() -{ - mir_cslock lck(PluginRegCS); - - // We remove protocols from the protocol list - while (FirstFilterPlugin != nullptr) - UnregisterFilterPlugin(FirstFilterPlugin->Plugin); - return 1; -} - -int WINAPI SetFilterPluginFcnImportFcn(HYAMNFILTERPLUGIN Plugin, uint32_t Importance, PYAMN_FILTERIMPORTFCN YAMNFilterFcn, uint32_t YAMNFilterFcnVer) -{ - PYAMN_FILTERPLUGINQUEUE Parser, Previous; - - if (YAMNFilterFcnVer != YAMN_FILTERIMPORTFCNVERSION) - return 0; - if (YAMNFilterFcn == nullptr) - return 0; - - Plugin->Importance = Importance; - Plugin->FilterFcn = YAMNFilterFcn; - - mir_cslock lck(PluginRegCS); - - // We add protocol to the protocol list - for (Previous = nullptr, Parser = FirstFilterPlugin; Parser != nullptr && Parser->Next != nullptr && Parser->Plugin->Importance <= Importance; Previous = Parser, Parser = Parser->Next); - - if (Previous == nullptr) { //insert to the beginnig of queue - FirstFilterPlugin = new YAMN_FILTERPLUGINQUEUE; - FirstFilterPlugin->Plugin = Plugin; - FirstFilterPlugin->Next = Parser; - } - else { - Previous->Next = new YAMN_FILTERPLUGINQUEUE; - Previous = Previous->Next; //leave previous, go to actual plugin - Previous->Plugin = Plugin; - Previous->Next = Parser; //and in actual plugin set, that next plugin is the one we insert in front of - } - return 1; -} - -INT_PTR FilterMailSvc(WPARAM wParam, LPARAM lParam) -{ - CAccount *Account = (CAccount *)wParam; - HYAMNMAIL Mail = (HYAMNMAIL)lParam; - PYAMN_FILTERPLUGINQUEUE ActualPlugin; - - mir_cslock lck(PluginRegCS); - WaitToWriteFcn(Account->MessagesAccessSO); - - for (ActualPlugin = FirstFilterPlugin; ActualPlugin != nullptr; ActualPlugin = ActualPlugin->Next) - if (ActualPlugin->Plugin->FilterFcn->FilterMailFcnPtr != nullptr) - ActualPlugin->Plugin->FilterFcn->FilterMailFcnPtr(Account, YAMN_ACCOUNTVERSION, Mail, YAMN_MAILVERSION); - - Mail->Flags |= YAMN_MSG_FILTERED; - - //Set mail flags according to spamlevel settings - if ((Mail->Flags & YAMN_MSG_SPAMMASK) > YAMN_MSG_SPAML1) - Mail->Flags = Mail->Flags & ~(YAMN_MSG_BROWSER | YAMN_MSG_POPUP | YAMN_MSG_SYSTRAY | YAMN_MSG_SOUND | YAMN_MSG_APP | YAMN_MSG_NEVENT); - if (YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML3) || YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML4)) - Mail->Flags = Mail->Flags | (YAMN_MSG_AUTODELETE | YAMN_MSG_DELETEOK); //set message to delete - if (YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML3)) - Mail->Flags = Mail->Flags & ~(YAMN_MSG_MEMDELETE); //set message not to delete it immidiatelly from memory - - WriteDoneFcn(Account->MessagesAccessSO); - return 1; -} +/* + * YAMN plugin export functions for filtering + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + + //-------------------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------------------- + +PYAMN_FILTERPLUGINQUEUE FirstFilterPlugin = nullptr; + +INT_PTR RegisterFilterPluginSvc(WPARAM, LPARAM); + +//Removes plugin from queue and deletes its structures +INT_PTR UnregisterFilterPlugin(HYAMNFILTERPLUGIN Plugin); + +INT_PTR UnregisterFilterPluginSvc(WPARAM wParam, LPARAM lParam); + +//Removes all filter plugins +INT_PTR UnregisterFilterPlugins(); + +INT_PTR FilterMailSvc(WPARAM, LPARAM); + +//Sets imported functions for an plugin and therefore it starts plugin to be registered and running +// Plugin- plugin, which wants to set its functions +// Importance- importance of plugin (see m_filterplugin.h) +// YAMNFilterFcn- pointer to imported functions +// YAMNfilterFcnVer- version of YAMN_FILTERIMPORTFCN, use YAMN_FILTERIMPORTFCNVERSION +// returns nonzero if success +int WINAPI SetFilterPluginFcnImportFcn(HYAMNFILTERPLUGIN Plugin, uint32_t Importance, PYAMN_FILTERIMPORTFCN YAMNFilterFcn, uint32_t YAMNFilterFcnVer); + +struct CExportedFunctions FilterPluginExportedFcn[] = +{ + {YAMN_SETFILTERPLUGINFCNIMPORTID, (void *)SetFilterPluginFcnImportFcn}, +}; + +struct CExportedServices FilterPluginExportedSvc[] = +{ + {MS_YAMN_REGISTERFILTERPLUGIN, RegisterFilterPluginSvc}, + {MS_YAMN_UNREGISTERFILTERPLUGIN, UnregisterFilterPluginSvc}, +}; + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +INT_PTR RegisterFilterPluginSvc(WPARAM wParam, LPARAM lParam) +{ + PYAMN_FILTERREGISTRATION Registration = (PYAMN_FILTERREGISTRATION)wParam; + HYAMNFILTERPLUGIN Plugin; + + if (lParam != YAMN_FILTERREGISTRATIONVERSION) + return 0; + if ((Registration->Name == nullptr) || (Registration->Ver == nullptr)) + return NULL; + if (nullptr == (Plugin = new YAMN_FILTERPLUGIN)) + return NULL; + + Plugin->PluginInfo = Registration; + Plugin->FilterFcn = nullptr; + return (INT_PTR)Plugin; +} + +INT_PTR UnregisterFilterPlugin(HYAMNFILTERPLUGIN Plugin) +{ + PYAMN_FILTERPLUGINQUEUE Parser, Found; + + if (FirstFilterPlugin->Plugin == Plugin) { + Found = FirstFilterPlugin; + FirstFilterPlugin = FirstFilterPlugin->Next; + } + else { + for (Parser = FirstFilterPlugin; (Parser->Next != nullptr) && (Plugin != Parser->Next->Plugin); Parser = Parser->Next); + if (Parser->Next != nullptr) { + Found = Parser->Next; + Parser->Next = Parser->Next->Next; + } + else + Found = nullptr; + } + if (Found != nullptr) { + if (Plugin->FilterFcn->UnLoadFcn != nullptr) + Plugin->FilterFcn->UnLoadFcn((void *)nullptr); + + delete Found->Plugin; + delete Found; + return 1; + } + + return 0; +} + +INT_PTR UnregisterFilterPluginSvc(WPARAM wParam, LPARAM) +{ + HYAMNFILTERPLUGIN Plugin = (HYAMNFILTERPLUGIN)wParam; + + mir_cslock lck(PluginRegCS); + UnregisterFilterPlugin(Plugin); + return 1; +} + +INT_PTR UnregisterFilterPlugins() +{ + mir_cslock lck(PluginRegCS); + + // We remove protocols from the protocol list + while (FirstFilterPlugin != nullptr) + UnregisterFilterPlugin(FirstFilterPlugin->Plugin); + return 1; +} + +int WINAPI SetFilterPluginFcnImportFcn(HYAMNFILTERPLUGIN Plugin, uint32_t Importance, PYAMN_FILTERIMPORTFCN YAMNFilterFcn, uint32_t YAMNFilterFcnVer) +{ + PYAMN_FILTERPLUGINQUEUE Parser, Previous; + + if (YAMNFilterFcnVer != YAMN_FILTERIMPORTFCNVERSION) + return 0; + if (YAMNFilterFcn == nullptr) + return 0; + + Plugin->Importance = Importance; + Plugin->FilterFcn = YAMNFilterFcn; + + mir_cslock lck(PluginRegCS); + + // We add protocol to the protocol list + for (Previous = nullptr, Parser = FirstFilterPlugin; Parser != nullptr && Parser->Next != nullptr && Parser->Plugin->Importance <= Importance; Previous = Parser, Parser = Parser->Next); + + if (Previous == nullptr) { //insert to the beginnig of queue + FirstFilterPlugin = new YAMN_FILTERPLUGINQUEUE; + FirstFilterPlugin->Plugin = Plugin; + FirstFilterPlugin->Next = Parser; + } + else { + Previous->Next = new YAMN_FILTERPLUGINQUEUE; + Previous = Previous->Next; //leave previous, go to actual plugin + Previous->Plugin = Plugin; + Previous->Next = Parser; //and in actual plugin set, that next plugin is the one we insert in front of + } + return 1; +} + +INT_PTR FilterMailSvc(WPARAM wParam, LPARAM lParam) +{ + CAccount *Account = (CAccount *)wParam; + HYAMNMAIL Mail = (HYAMNMAIL)lParam; + PYAMN_FILTERPLUGINQUEUE ActualPlugin; + + mir_cslock lck(PluginRegCS); + WaitToWriteFcn(Account->MessagesAccessSO); + + for (ActualPlugin = FirstFilterPlugin; ActualPlugin != nullptr; ActualPlugin = ActualPlugin->Next) + if (ActualPlugin->Plugin->FilterFcn->FilterMailFcnPtr != nullptr) + ActualPlugin->Plugin->FilterFcn->FilterMailFcnPtr(Account, YAMN_ACCOUNTVERSION, Mail, YAMN_MAILVERSION); + + Mail->Flags |= YAMN_MSG_FILTERED; + + //Set mail flags according to spamlevel settings + if ((Mail->Flags & YAMN_MSG_SPAMMASK) > YAMN_MSG_SPAML1) + Mail->Flags = Mail->Flags & ~(YAMN_MSG_BROWSER | YAMN_MSG_POPUP | YAMN_MSG_SYSTRAY | YAMN_MSG_SOUND | YAMN_MSG_APP | YAMN_MSG_NEVENT); + if (YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML3) || YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML4)) + Mail->Flags = Mail->Flags | (YAMN_MSG_AUTODELETE | YAMN_MSG_DELETEOK); //set message to delete + if (YAMN_MSG_SPAML(Mail->Flags, YAMN_MSG_SPAML3)) + Mail->Flags = Mail->Flags & ~(YAMN_MSG_MEMDELETE); //set message not to delete it immidiatelly from memory + + WriteDoneFcn(Account->MessagesAccessSO); + return 1; +} diff --git a/protocols/YAMN/src/mails/decode.cpp b/protocols/YAMN/src/mails/decode.cpp index 414edfce60..7306376c2e 100644 --- a/protocols/YAMN/src/mails/decode.cpp +++ b/protocols/YAMN/src/mails/decode.cpp @@ -1,534 +1,534 @@ -/* - * This code implements decoding encoded MIME header in style - * =?iso-8859-2?Q? "User using email in central Europe characters such as =E9" ?= - * - * (c) majvan 2002-2004 - */ -#include "../stdafx.h" -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -struct _tcptable CodePageNamesAll[]= -{ - { "ANSI", "",TRUE,CP_ACP}, - { "WINDOWS-1", "250",0,1250}, - { "WINDOWS-1", "251",0,1251}, - { "WINDOWS-1", "252",0,1252}, - { "WINDOWS-1", "253",0,1253}, - { "WINDOWS-1", "254",0,1254}, - { "WINDOWS-1", "255",0,1255}, - { "WINDOWS-1", "256",0,1256}, - { "WINDOWS-1", "257",0,1257}, - { "WINDOWS-1", "258",0,1258}, - { "CP1", "250",0,1250}, - { "CP1", "251",0,1251}, - { "CP1", "252",0,1252}, - { "CP1", "253",0,1253}, - { "CP1", "254",0,1254}, - { "CP1", "255",0,1255}, - { "CP1", "256",0,1256}, - { "CP1", "257",0,1257}, - { "CP1", "258",0,1258}, - { "ANSI-1", "250",0,1250}, - { "ANSI-1", "251",0,1251}, - { "ANSI-1", "252",0,1252}, - { "ANSI-1", "253",0,1253}, - { "ANSI-1", "254",0,1254}, - { "ANSI-1", "255",0,1255}, - { "ANSI-1", "256",0,1256}, - { "ANSI-1", "257",0,1257}, - { "ANSI-1", "258",0,1258}, - { "KOI8", "-R",0,20866}, - { "KOI8", "",0,20866}, - { "KOI8", "-U",0,21866}, - { "KOI8", "-RU",0,21866}, - { "US-", "ASCII",0,20127}, - { "CP", "367",0,20127}, - { "ASCII", "",0,20127}, - { "ASCII", "7",0,20127}, - { "ISO-8859", "-1",0,28591}, - { "ISO-8859", "-2",0,28592}, - { "ISO-8859", "-3",0,28593}, - { "ISO-8859", "-4",0,28594}, - { "ISO-8859", "-5",0,28595}, - { "ISO-8859", "-6",0,28596}, - { "ISO-8859", "-7",0,28597}, - { "ISO-8859", "-8",0,28598}, - { "ISO-8859", "-9",0,28599}, - { "ISO-8859", "-15",0,28605}, - { "ISO_8859", "-1",0,28591}, - { "ISO_8859", "-2",0,28592}, - { "ISO_8859", "-3",0,28593}, - { "ISO_8859", "-4",0,28594}, - { "ISO_8859", "-5",0,28595}, - { "ISO_8859", "-6",0,28596}, - { "ISO_8859", "-7",0,28597}, - { "ISO_8859", "-8",0,28598}, - { "ISO_8859", "-9",0,28599}, - { "ISO_8859", "-15",0,28605}, - { "ISO-", "10646-USC2",0,1200}, - { "ISO-2022", "/2-JP",0,50220}, - { "ISO-2022", "-JP",0,50221}, - { "ISO-2022", "/JIS-JP",0,50222}, - { "ISO-2022", "-KR",0,50225}, - { "ISO-2022", "-CH(SP)",0,50227}, - { "ISO-2022", "-CH(TR)",0,50229}, - { "UTF-", "7",0,65000}, - { "UTF-", "8",0,65001}, - { "ARAB-", "TRANSPARENT",0,710}, - { "ASMO-", "TRANSPARENT",0,720}, - { "ASMO-", "449",0,709}, - { "ASMO-", "708",0,708}, - { "BIG5", "",0,950}, - { "EUC-", "CH(SP)",0,51936}, - { "EUC-", "CH(TR)",0,51950}, - { "EUC-", "JP",0,51932}, - { "EUC-", "KR",0,51949}, - { "GB-", "2312",0,20936}, - { "GB", "2312",0,20936}, - { "HZGB-", "2312",0,52936}, - { "IBM-", "037",0,37}, - { "IBM-", "290",0,290}, - { "IBM-", "437",0,437}, - { "IBM-", "500",0,500}, - { "IBM-", "775",0,775}, - { "IBM-", "850",0,850}, - { "IBM-", "852",0,852}, - { "IBM-", "855",0,855}, - { "IBM-", "857",0,857}, - { "IBM-", "860",0,860}, - { "IBM-", "861",0,861}, - { "IBM-", "862",0,862}, - { "IBM-", "863",0,863}, - { "IBM-", "864",0,864}, - { "IBM-", "865",0,865}, - { "IBM-", "866",0,866}, - { "IBM-", "869",0,869}, - { "IBM-", "870",0,870}, - { "IBM-", "875",0,875}, - { "IBM-", "1026",0,1026}, - { "IBM-", "273",0,20273}, - { "IBM-", "277",0,20277}, - { "IBM-", "278",0,20278}, - { "IBM-", "280",0,20280}, - { "IBM-", "284",0,20284}, - { "IBM-", "285",0,20285}, - { "IBM-", "290",0,20290}, - { "IBM-", "297",0,20297}, - { "IBM-", "420",0,20420}, - { "IBM-", "423",0,20423}, - { "IBM-", "871",0,20871}, - { "IBM-", "880",0,20880}, - { "IBM-", "905",0,20905}, - { "IBM-", "THAI",0,20838}, - { "ISCII-", "DEVANAGARI",0,57002}, - { "ISCII-", "BENGALI",0,57003}, - { "ISCII-", "TAMIL",0,57004}, - { "ISCII-", "TELUGU",0,57005}, - { "ISCII-", "ASSAMESE",0,57006}, - { "ISCII-", "ORIYA",0,57007}, - { "ISCII-", "KANNADA",0,57008}, - { "ISCII-", "MALAYALAM",0,57009}, - { "ISCII-", "GUJARATI",0,57010}, - { "ISCII-", "PUNJABI",0,57011}, - { "KOR-", "JOHAB",0,1361}, - { "KSC-", "5601",0,1361}, - { "MAC-", "ROMAN",0,10000}, - { "MAC-", "JP",0,10001}, - { "MAC-", "CH(SP)(BIG5)",0,10002}, - { "MAC-", "KR",0,10003}, - { "MAC-", "AR",0,10004}, - { "MAC-", "HW",0,10005}, - { "MAC-", "GR",0,10006}, - { "MAC-", "CY",0,10007}, - { "MAC-", "CH(SP)(GB2312)",0,10008}, - { "MAC-", "ROMANIA",0,10010}, - { "MAC-", "UA",0,10017}, - { "MAC-", "TH",0,10021}, - { "MAC-", "LAT2",0,10029}, - { "MAC-", "ICE",0,10079}, - { "MAC-", "TR",0,10081}, - { "MAC-", "CR",0,10082} -}; - -int CPLENALL = _countof(CodePageNamesAll); -struct _tcptable *CodePageNamesSupp; -int CPLENSUPP = 1; - -//Gets codepage ID from string representing charset such as "iso-8859-1" -// input- the string -// size- max length of input string -int GetCharsetFromString(char *input, size_t size); - -//HexValue to DecValue ('a' to 10) -// HexValue- hexa value ('a') -// DecValue- poiner where to store dec value -// returns 0 if not success -int FromHexa(char HexValue, char *DecValue); - -//Decodes a char from Base64 -// Base64Value- input char in Base64 -// DecValue- pointer where to store the result -// returns 0 if not success -int FromBase64(char Base64Value, char *DecValue); - -//Decodes string in quoted printable -// Src- input string -// Dst- where to store output string -// DstLen- how max long should be output string -// isQ- if is "Q-encoding" modification. should be TRUE in headers -// always returns 1 -int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); - -//Decodes string in base64 -// Src- input string -// Dst- where to store output string -// DstLen- how max long should be output string -// returns 0 if string was not properly decoded -int DecodeBase64(char *Src, char *Dst, int DstLen); - -//Converts string to unicode from string with specified codepage -// stream- input string -// cp- codepage of input string -// out- pointer to new allocated memory that contains unicode string -int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); - -//Converts string from MIME header to unicode -// stream- input string -// cp- codepage of input string -// storeto- pointer to memory that contains unicode string -// mode- MIME_PLAIN or MIME_MAIL (MIME_MAIL deletes '"' from start and end of string) -void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -int GetCharsetFromString(char *input, size_t size) -//"ISO-8859-1" to ID from table -{ - char *pin = input; - char *pout, *parser; - - if ((size < 1) || (parser = pout = new char[size + 1]) == nullptr) - return -1; - while ((*pin != 0) && (pin - input < (INT_PTR)size)) { - if ((*pin >= 'a') && (*pin <= 'z')) - *parser++ = *(pin++) - ('a' - 'A'); // make it capital - //else if (*pin=='\"') // this is already done in ExtractFromContentType - // *pin++; //skip the quotes if any - else - *parser++ = *pin++; - } - - *parser = 0; - - #ifdef DEBUG_DECODECODEPAGE - DebugLog(DecodeFile, "%s", pout); - #endif - for (int i = 0; i < CPLENALL; i++) { - size_t len = mir_strlen(CodePageNamesAll[i].NameBase); - if (0 == strncmp(pout, CodePageNamesAll[i].NameBase, len)) { - if (0 == mir_strcmp(pout + len, CodePageNamesAll[i].NameSub)) { - delete[] pout; - return CodePageNamesAll[i].CP; - } - } - } - delete[] pout; - return -1; //not found -} - -int FromHexa(char HexValue, char *DecValue) -{ - if (HexValue >= '0' && HexValue <= '9') { - *DecValue = HexValue - '0'; - return 1; - } - if (HexValue >= 'A' && HexValue <= 'F') { - *DecValue = HexValue - 'A' + 10; - return 1; - } - if (HexValue >= 'a' && HexValue <= 'f') { - *DecValue = HexValue - 'a' + 10; - return 1; - } - return 0; -} - -int FromBase64(char Base64Value, char *DecValue) -{ - if (Base64Value >= 'A' && Base64Value <= 'Z') { - *DecValue = Base64Value - 'A'; - return 1; - } - if (Base64Value >= 'a' && Base64Value <= 'z') { - *DecValue = Base64Value - 'a' + 26; - return 1; - } - if (Base64Value >= '0' && Base64Value <= '9') { - *DecValue = Base64Value - '0' + 52; - return 1; - } - if (Base64Value == '+') { - *DecValue = Base64Value - '+' + 62; - return 1; - } - if (Base64Value == '/') { - *DecValue = Base64Value - '/' + 63; - return 1; - } - if (Base64Value == '=') { - *DecValue = 0; - return 1; - } - return 0; -} - -int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ) -{ - #ifdef DEBUG_DECODEQUOTED - char *DstTemp = Dst; - DebugLog(DecodeFile, "%s", Src); - #endif - for (int Counter = 0; (*Src != 0) && DstLen && (Counter++ < DstLen); Src++, Dst++) - if (*Src == '=') { - if (!isQ) { - if (Src[1] == 0x0D) { - Src++; Src++; - if (Src[0] == 0x0A) Src++; - goto CopyCharQuotedPrintable; - } - if (Src[1] == 0x0A) { - Src++; Src++; - goto CopyCharQuotedPrintable; - } - } - char First, Second; - if (!FromHexa(*(++Src), &First)) { - *Dst++ = '='; Src--; - continue; - } - if (!FromHexa(*(++Src), &Second)) { - *Dst++ = '='; Src--; Src--; - continue; - } - *Dst = (char)(First) << 4; - *Dst += Second; - } - else if (isQ && *Src == '_') - *Dst = ' '; - else - CopyCharQuotedPrintable: // Yeah. Bad programming stile. - *Dst = *Src; - *Dst = 0; - #ifdef DEBUG_DECODEQUOTED - DebugLog(DecodeFile, "%s", DstTemp); - #endif - return 1; -} - -int DecodeBase64(char *Src, char *Dst, int DstLen) -{ - int Result = 0; - char Locator = 0, MiniResult[4]; - char *End = Dst + DstLen; - - MiniResult[0] = MiniResult[1] = MiniResult[2] = MiniResult[3] = 0; - - #ifdef DEBUG_DECODEBASE64 - char *DstTemp = Dst; - DebugLog(DecodeFile, "\n%s\n\n", Src); - #endif - while (*Src != 0 && DstLen && Dst != End) { - if ((*Src == 0x0D) || (*Src == 0x0A)) { - Src++; - continue; - } - if ((!(Result = FromBase64(*Src, MiniResult + Locator)) && (*Src == 0)) || Locator++ == 3) //end_of_str || end_of_4_bytes - { - Locator = 0; //next write to the first byte - *Dst++ = (char)((MiniResult[0] << 2) | (MiniResult[1] >> 4)); - if (Dst == End) goto end; //DstLen exceeded? - *Dst++ = (char)((MiniResult[1] << 4) | (MiniResult[2] >> 2)); - if (Dst == End) goto end; //someones don't like goto, but not me - *Dst++ = (char)((MiniResult[2] << 6) | MiniResult[3]); - if (!Result && (*Src == 0)) goto end; //end of string? - MiniResult[0] = MiniResult[1] = MiniResult[2] = MiniResult[3] = 0; //zero 4byte buffer for next loop - } - if (!Result) return 0; //unrecognised character occured - Src++; - } -end: - *Dst = 0; - #ifdef DEBUG_DECODEBASE64 - DebugLog(DecodeFile, "\n%s\n", DstTemp); - #endif - return 1; -} - - - -int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out) -{ - CPINFO CPInfo; - wchar_t *temp, *src = *out, *dest; - size_t outlen; - int streamlen, Index; - - //codepages, which require to have set 0 in dwFlags parameter when calling MultiByteToWideChar - uint32_t CodePagesZeroFlags[] = {50220, 50221, 50222, 50225, 50227, 50229, 52936, 54936, 57002, 57003, 57004, 57005, 57006, 57007, 57008, 57009, 57010, 57011, 65000, 65001}; - - if ((cp != CP_ACP) && (cp != CP_OEMCP) && (cp != CP_MACCP) && (cp != CP_THREAD_ACP) && (cp != CP_SYMBOL) && (cp != CP_UTF7) && (cp != CP_UTF8) && !GetCPInfo(cp, &CPInfo)) - cp = CP_ACP; - #ifdef DEBUG_DECODECODEPAGE - DebugLog(DecodeFile, "%d", cp); - #endif - - for (Index = 0; Index < sizeof(CodePagesZeroFlags) / sizeof(CodePagesZeroFlags[0]); Index++) - if (CodePagesZeroFlags[Index] == cp) { - Index = -1; - break; - } - if (Index == -1) - streamlen = MultiByteToWideChar(cp, 0, stream, -1, nullptr, 0); - else - streamlen = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, stream, -1, nullptr, 0); - - if (*out != nullptr) - outlen = mir_wstrlen(*out); - else - outlen = 0; - temp = new wchar_t[streamlen + outlen + 1]; - - if (*out != nullptr) { - for (dest = temp; *src != (wchar_t)0; src++, dest++) //copy old string from *out to temp - *dest = *src; - // *dest++=L' '; //add space? - delete[] * out; - } - else - dest = temp; - *out = temp; - - if (Index == -1) { - if (!MultiByteToWideChar(cp, 0, stream, -1, dest, streamlen)) - return 0; - } - else { - if (!MultiByteToWideChar(cp, MB_USEGLYPHCHARS, stream, -1, dest, streamlen)) - return 0; - } - return 1; -} - -void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode) -{ - char *start = stream, *finder, *finderend; - char Encoding = 0; - - if (stream == nullptr) - return; - - while (WS(start)) start++; - wchar_t *tempstore = nullptr; - if (!ConvertStringToUnicode(stream, cp, &tempstore))return; - - size_t tempstoreLength = mir_wstrlen(tempstore); - - size_t outind = 0; - while (*start != 0) { - if (CODES(start)) { - finder = start + 2; finderend = finder; - while (!CODED(finderend) && !EOS(finderend)) finderend++; - start = finderend; - if (CODED(finderend)) { - Encoding = *(finderend + 1); - switch (Encoding) { - case 'b': - case 'B': - case 'q': - case 'Q': - break; - default: - goto NotEncoded; - } - if (-1 == (cp = (uint32_t)GetCharsetFromString(finder, finderend - finder))) - cp = CP_ACP; - if (Encoding != 0) { - int size = 0, codeend; - char *pcodeend = nullptr; - - finder = finderend + 2; - if (CODED(finder)) - finder++; - while (WS(finder)) finder++; - finderend = finder; - while (!CODEE(finderend) && !EOS(finderend)) finderend++; - if (codeend = CODEE(finderend)) - pcodeend = finderend; - while (WS(finderend - 1)) finderend--; - if ((mode == MIME_MAIL) && (((*finder == '"') && (*(finderend - 1) == '"')))) { - finder++; - finderend--; - } - char *oneWordEncoded = new char[finderend - finder + 1]; - strncpy(oneWordEncoded, finder, finderend - finder); - oneWordEncoded[finderend - finder] = 0; - switch (Encoding) { - case 'b': - case 'B': - size = (finderend - finder) * 3 / 4 + 3 + 1 + 1; - break; - case 'q': - case 'Q': - size = finderend - finder + 1 + 1; - break; - } - - char *DecodedResult = new char[size + 1]; - switch (Encoding) { - case 'q': - case 'Q': - DecodeQuotedPrintable(oneWordEncoded, DecodedResult, size, TRUE); - break; - case 'b': - case 'B': - DecodeBase64(oneWordEncoded, DecodedResult, size); - break; - } - delete[] oneWordEncoded; - if (codeend) - finderend = pcodeend + 2; - if (WS(finderend)) //if string continues and there's some whitespace, add space to string that is to be converted - { - size_t len = mir_strlen(DecodedResult); - DecodedResult[len] = ' '; - DecodedResult[len + 1] = 0; - finderend++; - } - wchar_t *oneWord = nullptr; - if (ConvertStringToUnicode(DecodedResult, cp, &oneWord)) { - size_t len = mir_wstrlen(oneWord); - memcpy(&tempstore[outind], oneWord, len * sizeof(wchar_t)); - outind += len; - } - delete oneWord; - oneWord = nullptr; - delete[] DecodedResult; - start = finderend; - } - else if (!EOS(start)) start++; - } - else if (!EOS(start)) start++; - } - else { -NotEncoded: - tempstore[outind] = tempstore[start - stream]; - outind++; - if (outind > tempstoreLength) break; - start++; - } - } - tempstore[outind] = 0; - *storeto = tempstore; -} +/* + * This code implements decoding encoded MIME header in style + * =?iso-8859-2?Q? "User using email in central Europe characters such as =E9" ?= + * + * (c) majvan 2002-2004 + */ +#include "../stdafx.h" +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +struct _tcptable CodePageNamesAll[]= +{ + { "ANSI", "",TRUE,CP_ACP}, + { "WINDOWS-1", "250",0,1250}, + { "WINDOWS-1", "251",0,1251}, + { "WINDOWS-1", "252",0,1252}, + { "WINDOWS-1", "253",0,1253}, + { "WINDOWS-1", "254",0,1254}, + { "WINDOWS-1", "255",0,1255}, + { "WINDOWS-1", "256",0,1256}, + { "WINDOWS-1", "257",0,1257}, + { "WINDOWS-1", "258",0,1258}, + { "CP1", "250",0,1250}, + { "CP1", "251",0,1251}, + { "CP1", "252",0,1252}, + { "CP1", "253",0,1253}, + { "CP1", "254",0,1254}, + { "CP1", "255",0,1255}, + { "CP1", "256",0,1256}, + { "CP1", "257",0,1257}, + { "CP1", "258",0,1258}, + { "ANSI-1", "250",0,1250}, + { "ANSI-1", "251",0,1251}, + { "ANSI-1", "252",0,1252}, + { "ANSI-1", "253",0,1253}, + { "ANSI-1", "254",0,1254}, + { "ANSI-1", "255",0,1255}, + { "ANSI-1", "256",0,1256}, + { "ANSI-1", "257",0,1257}, + { "ANSI-1", "258",0,1258}, + { "KOI8", "-R",0,20866}, + { "KOI8", "",0,20866}, + { "KOI8", "-U",0,21866}, + { "KOI8", "-RU",0,21866}, + { "US-", "ASCII",0,20127}, + { "CP", "367",0,20127}, + { "ASCII", "",0,20127}, + { "ASCII", "7",0,20127}, + { "ISO-8859", "-1",0,28591}, + { "ISO-8859", "-2",0,28592}, + { "ISO-8859", "-3",0,28593}, + { "ISO-8859", "-4",0,28594}, + { "ISO-8859", "-5",0,28595}, + { "ISO-8859", "-6",0,28596}, + { "ISO-8859", "-7",0,28597}, + { "ISO-8859", "-8",0,28598}, + { "ISO-8859", "-9",0,28599}, + { "ISO-8859", "-15",0,28605}, + { "ISO_8859", "-1",0,28591}, + { "ISO_8859", "-2",0,28592}, + { "ISO_8859", "-3",0,28593}, + { "ISO_8859", "-4",0,28594}, + { "ISO_8859", "-5",0,28595}, + { "ISO_8859", "-6",0,28596}, + { "ISO_8859", "-7",0,28597}, + { "ISO_8859", "-8",0,28598}, + { "ISO_8859", "-9",0,28599}, + { "ISO_8859", "-15",0,28605}, + { "ISO-", "10646-USC2",0,1200}, + { "ISO-2022", "/2-JP",0,50220}, + { "ISO-2022", "-JP",0,50221}, + { "ISO-2022", "/JIS-JP",0,50222}, + { "ISO-2022", "-KR",0,50225}, + { "ISO-2022", "-CH(SP)",0,50227}, + { "ISO-2022", "-CH(TR)",0,50229}, + { "UTF-", "7",0,65000}, + { "UTF-", "8",0,65001}, + { "ARAB-", "TRANSPARENT",0,710}, + { "ASMO-", "TRANSPARENT",0,720}, + { "ASMO-", "449",0,709}, + { "ASMO-", "708",0,708}, + { "BIG5", "",0,950}, + { "EUC-", "CH(SP)",0,51936}, + { "EUC-", "CH(TR)",0,51950}, + { "EUC-", "JP",0,51932}, + { "EUC-", "KR",0,51949}, + { "GB-", "2312",0,20936}, + { "GB", "2312",0,20936}, + { "HZGB-", "2312",0,52936}, + { "IBM-", "037",0,37}, + { "IBM-", "290",0,290}, + { "IBM-", "437",0,437}, + { "IBM-", "500",0,500}, + { "IBM-", "775",0,775}, + { "IBM-", "850",0,850}, + { "IBM-", "852",0,852}, + { "IBM-", "855",0,855}, + { "IBM-", "857",0,857}, + { "IBM-", "860",0,860}, + { "IBM-", "861",0,861}, + { "IBM-", "862",0,862}, + { "IBM-", "863",0,863}, + { "IBM-", "864",0,864}, + { "IBM-", "865",0,865}, + { "IBM-", "866",0,866}, + { "IBM-", "869",0,869}, + { "IBM-", "870",0,870}, + { "IBM-", "875",0,875}, + { "IBM-", "1026",0,1026}, + { "IBM-", "273",0,20273}, + { "IBM-", "277",0,20277}, + { "IBM-", "278",0,20278}, + { "IBM-", "280",0,20280}, + { "IBM-", "284",0,20284}, + { "IBM-", "285",0,20285}, + { "IBM-", "290",0,20290}, + { "IBM-", "297",0,20297}, + { "IBM-", "420",0,20420}, + { "IBM-", "423",0,20423}, + { "IBM-", "871",0,20871}, + { "IBM-", "880",0,20880}, + { "IBM-", "905",0,20905}, + { "IBM-", "THAI",0,20838}, + { "ISCII-", "DEVANAGARI",0,57002}, + { "ISCII-", "BENGALI",0,57003}, + { "ISCII-", "TAMIL",0,57004}, + { "ISCII-", "TELUGU",0,57005}, + { "ISCII-", "ASSAMESE",0,57006}, + { "ISCII-", "ORIYA",0,57007}, + { "ISCII-", "KANNADA",0,57008}, + { "ISCII-", "MALAYALAM",0,57009}, + { "ISCII-", "GUJARATI",0,57010}, + { "ISCII-", "PUNJABI",0,57011}, + { "KOR-", "JOHAB",0,1361}, + { "KSC-", "5601",0,1361}, + { "MAC-", "ROMAN",0,10000}, + { "MAC-", "JP",0,10001}, + { "MAC-", "CH(SP)(BIG5)",0,10002}, + { "MAC-", "KR",0,10003}, + { "MAC-", "AR",0,10004}, + { "MAC-", "HW",0,10005}, + { "MAC-", "GR",0,10006}, + { "MAC-", "CY",0,10007}, + { "MAC-", "CH(SP)(GB2312)",0,10008}, + { "MAC-", "ROMANIA",0,10010}, + { "MAC-", "UA",0,10017}, + { "MAC-", "TH",0,10021}, + { "MAC-", "LAT2",0,10029}, + { "MAC-", "ICE",0,10079}, + { "MAC-", "TR",0,10081}, + { "MAC-", "CR",0,10082} +}; + +int CPLENALL = _countof(CodePageNamesAll); +struct _tcptable *CodePageNamesSupp; +int CPLENSUPP = 1; + +//Gets codepage ID from string representing charset such as "iso-8859-1" +// input- the string +// size- max length of input string +int GetCharsetFromString(char *input, size_t size); + +//HexValue to DecValue ('a' to 10) +// HexValue- hexa value ('a') +// DecValue- poiner where to store dec value +// returns 0 if not success +int FromHexa(char HexValue, char *DecValue); + +//Decodes a char from Base64 +// Base64Value- input char in Base64 +// DecValue- pointer where to store the result +// returns 0 if not success +int FromBase64(char Base64Value, char *DecValue); + +//Decodes string in quoted printable +// Src- input string +// Dst- where to store output string +// DstLen- how max long should be output string +// isQ- if is "Q-encoding" modification. should be TRUE in headers +// always returns 1 +int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); + +//Decodes string in base64 +// Src- input string +// Dst- where to store output string +// DstLen- how max long should be output string +// returns 0 if string was not properly decoded +int DecodeBase64(char *Src, char *Dst, int DstLen); + +//Converts string to unicode from string with specified codepage +// stream- input string +// cp- codepage of input string +// out- pointer to new allocated memory that contains unicode string +int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); + +//Converts string from MIME header to unicode +// stream- input string +// cp- codepage of input string +// storeto- pointer to memory that contains unicode string +// mode- MIME_PLAIN or MIME_MAIL (MIME_MAIL deletes '"' from start and end of string) +void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +int GetCharsetFromString(char *input, size_t size) +//"ISO-8859-1" to ID from table +{ + char *pin = input; + char *pout, *parser; + + if ((size < 1) || (parser = pout = new char[size + 1]) == nullptr) + return -1; + while ((*pin != 0) && (pin - input < (INT_PTR)size)) { + if ((*pin >= 'a') && (*pin <= 'z')) + *parser++ = *(pin++) - ('a' - 'A'); // make it capital + //else if (*pin=='\"') // this is already done in ExtractFromContentType + // *pin++; //skip the quotes if any + else + *parser++ = *pin++; + } + + *parser = 0; + + #ifdef DEBUG_DECODECODEPAGE + DebugLog(DecodeFile, "%s", pout); + #endif + for (int i = 0; i < CPLENALL; i++) { + size_t len = mir_strlen(CodePageNamesAll[i].NameBase); + if (0 == strncmp(pout, CodePageNamesAll[i].NameBase, len)) { + if (0 == mir_strcmp(pout + len, CodePageNamesAll[i].NameSub)) { + delete[] pout; + return CodePageNamesAll[i].CP; + } + } + } + delete[] pout; + return -1; //not found +} + +int FromHexa(char HexValue, char *DecValue) +{ + if (HexValue >= '0' && HexValue <= '9') { + *DecValue = HexValue - '0'; + return 1; + } + if (HexValue >= 'A' && HexValue <= 'F') { + *DecValue = HexValue - 'A' + 10; + return 1; + } + if (HexValue >= 'a' && HexValue <= 'f') { + *DecValue = HexValue - 'a' + 10; + return 1; + } + return 0; +} + +int FromBase64(char Base64Value, char *DecValue) +{ + if (Base64Value >= 'A' && Base64Value <= 'Z') { + *DecValue = Base64Value - 'A'; + return 1; + } + if (Base64Value >= 'a' && Base64Value <= 'z') { + *DecValue = Base64Value - 'a' + 26; + return 1; + } + if (Base64Value >= '0' && Base64Value <= '9') { + *DecValue = Base64Value - '0' + 52; + return 1; + } + if (Base64Value == '+') { + *DecValue = Base64Value - '+' + 62; + return 1; + } + if (Base64Value == '/') { + *DecValue = Base64Value - '/' + 63; + return 1; + } + if (Base64Value == '=') { + *DecValue = 0; + return 1; + } + return 0; +} + +int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ) +{ + #ifdef DEBUG_DECODEQUOTED + char *DstTemp = Dst; + DebugLog(DecodeFile, "%s", Src); + #endif + for (int Counter = 0; (*Src != 0) && DstLen && (Counter++ < DstLen); Src++, Dst++) + if (*Src == '=') { + if (!isQ) { + if (Src[1] == 0x0D) { + Src++; Src++; + if (Src[0] == 0x0A) Src++; + goto CopyCharQuotedPrintable; + } + if (Src[1] == 0x0A) { + Src++; Src++; + goto CopyCharQuotedPrintable; + } + } + char First, Second; + if (!FromHexa(*(++Src), &First)) { + *Dst++ = '='; Src--; + continue; + } + if (!FromHexa(*(++Src), &Second)) { + *Dst++ = '='; Src--; Src--; + continue; + } + *Dst = (char)(First) << 4; + *Dst += Second; + } + else if (isQ && *Src == '_') + *Dst = ' '; + else + CopyCharQuotedPrintable: // Yeah. Bad programming stile. + *Dst = *Src; + *Dst = 0; + #ifdef DEBUG_DECODEQUOTED + DebugLog(DecodeFile, "%s", DstTemp); + #endif + return 1; +} + +int DecodeBase64(char *Src, char *Dst, int DstLen) +{ + int Result = 0; + char Locator = 0, MiniResult[4]; + char *End = Dst + DstLen; + + MiniResult[0] = MiniResult[1] = MiniResult[2] = MiniResult[3] = 0; + + #ifdef DEBUG_DECODEBASE64 + char *DstTemp = Dst; + DebugLog(DecodeFile, "\n%s\n\n", Src); + #endif + while (*Src != 0 && DstLen && Dst != End) { + if ((*Src == 0x0D) || (*Src == 0x0A)) { + Src++; + continue; + } + if ((!(Result = FromBase64(*Src, MiniResult + Locator)) && (*Src == 0)) || Locator++ == 3) //end_of_str || end_of_4_bytes + { + Locator = 0; //next write to the first byte + *Dst++ = (char)((MiniResult[0] << 2) | (MiniResult[1] >> 4)); + if (Dst == End) goto end; //DstLen exceeded? + *Dst++ = (char)((MiniResult[1] << 4) | (MiniResult[2] >> 2)); + if (Dst == End) goto end; //someones don't like goto, but not me + *Dst++ = (char)((MiniResult[2] << 6) | MiniResult[3]); + if (!Result && (*Src == 0)) goto end; //end of string? + MiniResult[0] = MiniResult[1] = MiniResult[2] = MiniResult[3] = 0; //zero 4byte buffer for next loop + } + if (!Result) return 0; //unrecognised character occured + Src++; + } +end: + *Dst = 0; + #ifdef DEBUG_DECODEBASE64 + DebugLog(DecodeFile, "\n%s\n", DstTemp); + #endif + return 1; +} + + + +int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out) +{ + CPINFO CPInfo; + wchar_t *temp, *src = *out, *dest; + size_t outlen; + int streamlen, Index; + + //codepages, which require to have set 0 in dwFlags parameter when calling MultiByteToWideChar + uint32_t CodePagesZeroFlags[] = {50220, 50221, 50222, 50225, 50227, 50229, 52936, 54936, 57002, 57003, 57004, 57005, 57006, 57007, 57008, 57009, 57010, 57011, 65000, 65001}; + + if ((cp != CP_ACP) && (cp != CP_OEMCP) && (cp != CP_MACCP) && (cp != CP_THREAD_ACP) && (cp != CP_SYMBOL) && (cp != CP_UTF7) && (cp != CP_UTF8) && !GetCPInfo(cp, &CPInfo)) + cp = CP_ACP; + #ifdef DEBUG_DECODECODEPAGE + DebugLog(DecodeFile, "%d", cp); + #endif + + for (Index = 0; Index < sizeof(CodePagesZeroFlags) / sizeof(CodePagesZeroFlags[0]); Index++) + if (CodePagesZeroFlags[Index] == cp) { + Index = -1; + break; + } + if (Index == -1) + streamlen = MultiByteToWideChar(cp, 0, stream, -1, nullptr, 0); + else + streamlen = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, stream, -1, nullptr, 0); + + if (*out != nullptr) + outlen = mir_wstrlen(*out); + else + outlen = 0; + temp = new wchar_t[streamlen + outlen + 1]; + + if (*out != nullptr) { + for (dest = temp; *src != (wchar_t)0; src++, dest++) //copy old string from *out to temp + *dest = *src; + // *dest++=L' '; //add space? + delete[] * out; + } + else + dest = temp; + *out = temp; + + if (Index == -1) { + if (!MultiByteToWideChar(cp, 0, stream, -1, dest, streamlen)) + return 0; + } + else { + if (!MultiByteToWideChar(cp, MB_USEGLYPHCHARS, stream, -1, dest, streamlen)) + return 0; + } + return 1; +} + +void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode) +{ + char *start = stream, *finder, *finderend; + char Encoding = 0; + + if (stream == nullptr) + return; + + while (WS(start)) start++; + wchar_t *tempstore = nullptr; + if (!ConvertStringToUnicode(stream, cp, &tempstore))return; + + size_t tempstoreLength = mir_wstrlen(tempstore); + + size_t outind = 0; + while (*start != 0) { + if (CODES(start)) { + finder = start + 2; finderend = finder; + while (!CODED(finderend) && !EOS(finderend)) finderend++; + start = finderend; + if (CODED(finderend)) { + Encoding = *(finderend + 1); + switch (Encoding) { + case 'b': + case 'B': + case 'q': + case 'Q': + break; + default: + goto NotEncoded; + } + if (-1 == (cp = (uint32_t)GetCharsetFromString(finder, finderend - finder))) + cp = CP_ACP; + if (Encoding != 0) { + int size = 0, codeend; + char *pcodeend = nullptr; + + finder = finderend + 2; + if (CODED(finder)) + finder++; + while (WS(finder)) finder++; + finderend = finder; + while (!CODEE(finderend) && !EOS(finderend)) finderend++; + if (codeend = CODEE(finderend)) + pcodeend = finderend; + while (WS(finderend - 1)) finderend--; + if ((mode == MIME_MAIL) && (((*finder == '"') && (*(finderend - 1) == '"')))) { + finder++; + finderend--; + } + char *oneWordEncoded = new char[finderend - finder + 1]; + strncpy(oneWordEncoded, finder, finderend - finder); + oneWordEncoded[finderend - finder] = 0; + switch (Encoding) { + case 'b': + case 'B': + size = (finderend - finder) * 3 / 4 + 3 + 1 + 1; + break; + case 'q': + case 'Q': + size = finderend - finder + 1 + 1; + break; + } + + char *DecodedResult = new char[size + 1]; + switch (Encoding) { + case 'q': + case 'Q': + DecodeQuotedPrintable(oneWordEncoded, DecodedResult, size, TRUE); + break; + case 'b': + case 'B': + DecodeBase64(oneWordEncoded, DecodedResult, size); + break; + } + delete[] oneWordEncoded; + if (codeend) + finderend = pcodeend + 2; + if (WS(finderend)) //if string continues and there's some whitespace, add space to string that is to be converted + { + size_t len = mir_strlen(DecodedResult); + DecodedResult[len] = ' '; + DecodedResult[len + 1] = 0; + finderend++; + } + wchar_t *oneWord = nullptr; + if (ConvertStringToUnicode(DecodedResult, cp, &oneWord)) { + size_t len = mir_wstrlen(oneWord); + memcpy(&tempstore[outind], oneWord, len * sizeof(wchar_t)); + outind += len; + } + delete oneWord; + oneWord = nullptr; + delete[] DecodedResult; + start = finderend; + } + else if (!EOS(start)) start++; + } + else if (!EOS(start)) start++; + } + else { +NotEncoded: + tempstore[outind] = tempstore[start - stream]; + outind++; + if (outind > tempstoreLength) break; + start++; + } + } + tempstore[outind] = 0; + *storeto = tempstore; +} diff --git a/protocols/YAMN/src/mails/mails.cpp b/protocols/YAMN/src/mails/mails.cpp index 168c6c9397..1385cb649d 100644 --- a/protocols/YAMN/src/mails/mails.cpp +++ b/protocols/YAMN/src/mails/mails.cpp @@ -1,473 +1,473 @@ -/* - * This code implements retrieving info from MIME header - * - * (c) majvan 2002-2004 - */ - -#include "../stdafx.h" - - //-------------------------------------------------------------------------------------------------- - //-------------------------------------------------------------------------------------------------- - - // SMALL INTRO - // Mails are queued in a queue (chained list). Pointer to first mail is pointed from Account structure - // member called Mails. - // Mail queue is ended with NULL- pointered mail (NULL handle) - - //Creates new mail for plugin (calling plugin's constructor, when plugin imported to YAMN) -INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam); - -//Deletes mail for plugin (calling plugin's destructor, when plugin imported to YAMN) -INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam); - -//Loads mail data from standard storage to memory -INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam); - -//Deletes mail data from memory -INT_PTR UnloadMailDataSvc(WPARAM wParam, LPARAM); - -//Saves mail data from memory to standard storage -INT_PTR SaveMailDataSvc(WPARAM wParam, LPARAM lParam); - -//Appends second MIME mail queue to the first one -//Only finds the end of first queue and its Next memember repoints to second one -void WINAPI AppendQueueFcn(HYAMNMAIL first, HYAMNMAIL second); - -//Synchronizes two accounts -//Function finds, if there were some mails deleted from mailbox and deletes (depends on RemovedOld param) them from OldQueue -//Next finds, if there are new mails. Mails that are still on mailbox are deleted (depends on RemovedNew param) from NewQueue -//After this, OldQueue is pointer to mails that are on mailbox, but not new mails -//and NewQueue contains new mails in account -//New accounts can be then appended to account mails queue, but they have set the New flag -// -//Two mails equals if they have the same ID -// -// hPlugin- handle of plugin going to delete mails -// OldQueue- queue of mails that we found on mailbox last time, after function finishes queue contains all mails except new ones -// RemovedOld- queue of mails where to store removed mails from OldQueue, if NULL deletes mails from OldQueue -// NewQueue- queue of mails that we found on mailbox (all mails), after function finishes queue contains only new mails -// RemovedNew- queue of mails where to store removed mails from NewQueue, if NULL deletes mails from NewQueue -//So function works like: -//1. delete (or move to RemovedOld queue if RemovedOld is not NULL) all mails from OldQueue not found in NewQueue -//2. delete (or move to RemovedNew queue if RemovedNew is not NULL) all mails from NewQueue found in OldQueue -void WINAPI SynchroMessagesFcn(CAccount *Account, HYAMNMAIL *OldQueue, HYAMNMAIL *RemovedOld, HYAMNMAIL *NewQueue, HYAMNMAIL *RemovedNew); - -//Deletes messages from mail From to the end -// Account- account who owns mails -// From- first mail in queue, which is going to delete -void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From); - -//Removes message from queue, does not delete from memory -// From- queue pointer -// Which- mail to delete -// mode- nonzero if you want to decrement numbers in messages that are bigger than the one in Which mail, 0 if not -void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode); - -//Finds message in queue that has the same ID number -// From- message queue -// ID- pointer to ID -// returns pointer to found message, NULL if not found -HYAMNMAIL WINAPI FindMessageByIDFcn(HYAMNMAIL From, char *ID); - -//Translate header from text to queue of CMimeItem structures -//This means that new queue will contain all info about headers -// stream- pointer to text containing header (can be ended with zero) -// len- length of stream -// head- function fills this pointer to first header item in queue -void WINAPI TranslateHeaderFcn(char *stream, int len, struct CMimeItem **head); - -//Creates new mail queue, copying only these mails, that have set flag for deleting -// From- message queue, whose mail with given flag are duplicated -// returns new mail queue (or NULL when no mail with flag is in From queue) -//Function does not copy the whole mails, it copies only ID string. And ID is copied as string, so -//you can use this fcn only if you have your ID as pointer to char string ended with zero character -HYAMNMAIL WINAPI CreateNewDeleteQueueFcn(HYAMNMAIL From); - -//Sets/removes flags from specific mails -// From- pointer to first message -// FlagsSet- mail must have set these flags... -// FlagsNotSet- ...and must not have set these flags... -// FlagsToSetRemove- ...to set/remove these flags (see mode) -// mode- nonzero to set, else remove -void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSetRemove, int mode); - -struct CExportedFunctions MailExportedFcn[] = -{ - {YAMN_SYNCHROMIMEMSGSID, (void *)SynchroMessagesFcn}, - {YAMN_TRANSLATEHEADERID, (void *)TranslateHeaderFcn}, - {YAMN_APPENDQUEUEID, (void *)AppendQueueFcn}, - {YAMN_DELETEMIMEQUEUEID, (void *)DeleteMessagesToEndFcn}, - {YAMN_DELETEMIMEMESSAGEID, (void *)DeleteMessageFromQueueFcn}, - {YAMN_FINDMIMEMESSAGEID, (void *)FindMessageByIDFcn}, - {YAMN_CREATENEWDELETEQUEUEID, (void *)CreateNewDeleteQueueFcn}, - {YAMN_SETREMOVEQUEUEFLAGSID, (void *)SetRemoveFlagsInQueueFcn}, -}; - -struct CExportedServices MailExportedSvc[] = -{ - {MS_YAMN_CREATEACCOUNTMAIL, CreateAccountMailSvc}, - {MS_YAMN_DELETEACCOUNTMAIL, DeleteAccountMailSvc}, - {MS_YAMN_LOADMAILDATA, LoadMailDataSvc}, - {MS_YAMN_UNLOADMAILDATA, UnloadMailDataSvc}, - {MS_YAMN_SAVEMAILDATA, SaveMailDataSvc}, -}; - - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam) -{ - CAccount *Account = (CAccount *)wParam; - uint32_t MailVersion = (uint32_t)lParam; - HYAMNMAIL NewMail; - - //test if we are going to initialize members of suitable structure (structures of plugin and YAMN must match) - if (MailVersion != YAMN_MAILVERSION) - return NULL; - - if (Account->Plugin != nullptr) { - if (Account->Plugin->MailFcn->NewMailFcnPtr != nullptr) { - //Let plugin create its own structure, which can be derived from CAccount structure - if (nullptr == (NewMail = Account->Plugin->MailFcn->NewMailFcnPtr(Account, YAMN_MAILVERSION))) - return NULL; - } - else { - //We suggest plugin uses standard CAccount structure, so we create it - if (nullptr == (NewMail = new YAMNMAIL)) - //If not created successfully - return NULL; - NewMail->MailData = nullptr; - } - //Init every members of structure, used by YAMN - return (INT_PTR)NewMail; - } - return NULL; -} - -INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - HYAMNMAIL OldMail = (HYAMNMAIL)lParam; - struct CMimeItem *TH; - - if (Plugin->MailFcn != nullptr) { - if (Plugin->MailFcn->DeleteMailFcnPtr != nullptr) { - //Let plugin delete its own CMimeMsgQueue derived structure - Plugin->MailFcn->DeleteMailFcnPtr(OldMail); - return 1; - } - } - if (OldMail->MailData != nullptr) { - if (OldMail->MailData->Body != nullptr) - delete[] OldMail->MailData->Body; - if ((TH = OldMail->MailData->TranslatedHeader) != nullptr) - for (; OldMail->MailData->TranslatedHeader != nullptr;) { - TH = TH->Next; - if (OldMail->MailData->TranslatedHeader->name != nullptr) - delete[] OldMail->MailData->TranslatedHeader->name; - if (OldMail->MailData->TranslatedHeader->value != nullptr) - delete[] OldMail->MailData->TranslatedHeader->value; - delete OldMail->MailData->TranslatedHeader; - OldMail->MailData->TranslatedHeader = TH; - } - delete OldMail->MailData; - } - if (OldMail->ID != nullptr) - delete[] OldMail->ID; - - delete OldMail; //consider mail as standard HYAMNMAIL, not initialized before and use its own destructor - return 1; -} - - -void WINAPI AppendQueueFcn(HYAMNMAIL first, HYAMNMAIL second) -{ - HYAMNMAIL Finder = first; - while (Finder->Next != nullptr) Finder = Finder->Next; - Finder->Next = second; -} - -INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam) -{ - HYAMNMAIL Mail = (HYAMNMAIL)wParam; - uint32_t MailVersion = (uint32_t)lParam; - - if (MailVersion != YAMN_MAILDATAVERSION) - return NULL; - - // now we have all data to memory persisting, so no loading is needed - return (INT_PTR)Mail->MailData; -} - -INT_PTR UnloadMailDataSvc(WPARAM, LPARAM) -{ - return 1; -} - -INT_PTR SaveMailDataSvc(WPARAM, LPARAM lParam) -{ - uint32_t MailVersion = (uint32_t)lParam; - - if (MailVersion != YAMN_MAILDATAVERSION) - return (INT_PTR)-1; - - // now we have all data to memory persisting, so no saving is needed - return (INT_PTR)0; -} - -void WINAPI SynchroMessagesFcn(CAccount *Account, HYAMNMAIL *OldQueue, HYAMNMAIL *RemovedOld, HYAMNMAIL *NewQueue, HYAMNMAIL *RemovedNew) -//deletes messages from new queue, if they are old -//it also deletes messages from old queue, if they are not in mailbox anymore -//"YAMN_MSG_DELETED" messages in old queue remain in old queue (are never removed, although they are not in new queue) -//"YAMN_MSG_DELETED" messages in new queue remain in new queue (are never removed, although they can be in old queue) -{ - HYAMNMAIL Finder, FinderPrev; - HYAMNMAIL Parser, ParserPrev; - HYAMNMAIL RemovedOldParser = nullptr; - HYAMNMAIL RemovedNewParser = nullptr; - if (RemovedOld != nullptr) *RemovedOld = nullptr; - if (RemovedNew != nullptr) *RemovedNew = nullptr; - - for (FinderPrev = nullptr, Finder = *OldQueue; Finder != nullptr;) { - if (Finder->Flags & YAMN_MSG_DELETED) //if old queue contains deleted mail - { - FinderPrev = Finder; - Finder = Finder->Next; //get next message in old queue for testing - continue; - } - for (ParserPrev = nullptr, Parser = *NewQueue; Parser != nullptr; ParserPrev = Parser, Parser = Parser->Next) { - if (Parser->Flags & YAMN_MSG_DELETED) - continue; - - if (Parser->ID == nullptr) //simply ignore the message, that has not filled its ID - continue; - - if (0 == mir_strcmp(Parser->ID, Finder->ID)) //search for equal message in new queue - break; - } - if (Parser != nullptr) //found equal message in new queue - { - if (Parser == *NewQueue) - *NewQueue = (*NewQueue)->Next; - else - ParserPrev->Next = Parser->Next; - Finder->Number = Parser->Number; //rewrite the number of current message in old queue - - if (RemovedNew == nullptr) //delete from new queue - DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Parser); - else //or move to RemovedNew - { - if (RemovedNewParser == nullptr) //if it is first mail removed from NewQueue - *RemovedNew = Parser; //set RemovedNew queue to point to first message in removed queue - else - RemovedNewParser->Next = Parser; //else don't forget to show to next message in RemovedNew queue - RemovedNewParser = Parser; //follow RemovedNew queue - RemovedNewParser->Next = nullptr; - } - FinderPrev = Finder; - Finder = Finder->Next; //get next message in old queue for testing - } - else //a message was already deleted from mailbox - { - if (Finder == *OldQueue) //if we are at the first item in OldQueue - { - *OldQueue = (*OldQueue)->Next; //set OldQueue to next item - if (RemovedOld == nullptr) //delete from old queue - DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Finder); - else //or move to RemovedOld - { - if (RemovedOldParser == nullptr) //if it is first mail removed from OldQueue - *RemovedOld = Finder; //set RemovedOld queue to point to first message in removed queue - else - RemovedOldParser->Next = Finder; //else don't forget to show to next message in RemovedNew queue - RemovedOldParser = Finder; //follow RemovedOld queue - RemovedOldParser->Next = nullptr; - } - Finder = *OldQueue; - } - else { - FinderPrev->Next = Finder->Next; - if (RemovedOld == nullptr) //delete from old queue - DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Finder); - else //or move to RemovedOld - { - if (RemovedOldParser == nullptr) //if it is first mail removed from OldQueue - *RemovedOld = Finder; //set RemovedOld queue to point to first message in removed queue - else - RemovedOldParser->Next = Finder; //else don't forget to show to next message in RemovedNew queue - RemovedOldParser = Finder; //follow RemovedOld queue - RemovedOldParser->Next = nullptr; - } - Finder = FinderPrev->Next; - } - } - } -} - -void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From) -{ - HYAMNMAIL Temp; - while (From != nullptr) { - Temp = From; - From = From->Next; - DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Temp); - } -} - -void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode = 0) -{ - uint32_t Number = Which->Number; - HYAMNMAIL Parser; - - if (*From == Which) { - Parser = Which->Next; - *From = Parser; - } - else { - for (Parser = *From; Which != Parser->Next; Parser = Parser->Next) - if (mode && (Parser->Number > Number)) Parser->Number--; - if (mode && (Parser->Number > Number)) Parser->Number--; - Parser->Next = Parser->Next->Next; - Parser = Which->Next; - } - if (mode) - for (; Parser != nullptr; Parser = Parser->Next) - if (Parser->Number > Number) Parser->Number--; -} - -void DeleteMessagesFromQueue(HYAMNMAIL *From, HYAMNMAIL Which, int mode = 0) -{ - HYAMNMAIL Parser; - - for (Parser = Which; Parser != nullptr; Parser = Parser->Next) - DeleteMessageFromQueueFcn(From, Parser, mode); -} - -HYAMNMAIL WINAPI FindMessageByIDFcn(HYAMNMAIL From, char *ID) -{ - HYAMNMAIL Browser; - - for (Browser = From; Browser != nullptr; Browser = Browser->Next) - if (0 == mir_strcmp(Browser->ID, ID)) - break; - return Browser; -} - -void WINAPI TranslateHeaderFcn(char *stream, int len, struct CMimeItem **head) -{ - try { - char *finder = stream; - char *prev1, *prev2, *prev3; - struct CMimeItem *Item = nullptr; - - while (finder <= (stream + len)) { - while (ENDLINEWS(finder)) finder++; - - //at the start of line - if (DOTLINE(finder + 1)) //at the end of stream - break; - - prev1 = finder; - - while (*finder != ':' && !EOS(finder)) finder++; - if (!EOS(finder)) - prev2 = finder++; - else - break; - - while (WS(finder) && !EOS(finder)) finder++; - if (!EOS(finder)) - prev3 = finder; - else - break; - - do { - if (ENDLINEWS(finder)) finder += 2; //after endline information continues - while (!ENDLINE(finder) && !EOS(finder)) finder++; - } while (ENDLINEWS(finder)); - - if (Item != nullptr) { - if (nullptr == (Item->Next = new struct CMimeItem)) - break; - Item = Item->Next; - } - else { - Item = new CMimeItem; - *head = Item; - } - - Item->Next = nullptr; - Item->name = new char[prev2 - prev1 + 1]; - mir_strncpy(Item->name, prev1, prev2 - prev1 + 1); - Item->value = new char[finder - prev3 + 1]; - mir_strncpy(Item->value, prev3, finder - prev3 + 1); - - if (EOS(finder)) - break; - finder++; - if (ENDLINE(finder)) { - finder++; - if (ENDLINE(finder)) { - // end of headers. message body begins - finder++; - if (ENDLINE(finder))finder++; - prev1 = finder; - while (!DOTLINE(finder + 1))finder++; - if (ENDLINE(finder))finder--; - prev2 = finder; - if (prev2 > prev1) { // yes, we have body - if (nullptr == (Item->Next = new struct CMimeItem)) break; // Cant create new item?! - Item = Item->Next; - Item->Next = nullptr;//just in case; - Item->name = new char[5]; strncpy(Item->name, "Body", 5); - Item->value = new char[prev2 - prev1]; - mir_strncpy(Item->value, prev1, prev2 - prev1 - 1); - } - break; // there is nothing else - } - } - } - } - catch (...) { - MessageBoxA(nullptr, "Translate header error", "", 0); - } -} - -HYAMNMAIL WINAPI CreateNewDeleteQueueFcn(HYAMNMAIL From) -{ - HYAMNMAIL FirstMail, Browser = nullptr; - - for (FirstMail = nullptr; From != nullptr; From = From->Next) { - if ((From->Flags & (YAMN_MSG_USERDELETE | YAMN_MSG_AUTODELETE)) && !(From->Flags & YAMN_MSG_DELETED)) { - if (FirstMail == nullptr) { - FirstMail = Browser = new YAMNMAIL; - if (FirstMail == nullptr) - break; - } - else { - Browser->Next = new YAMNMAIL; - Browser = Browser->Next; - } - Browser->ID = new char[mir_strlen(From->ID) + 1]; - mir_strcpy(Browser->ID, From->ID); - Browser->Number = From->Number; - Browser->Flags = From->Flags; - } - } - return FirstMail; -} - -void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSetRemove, int mode) -{ - HYAMNMAIL msgq; - - for (msgq = (HYAMNMAIL)From; msgq != nullptr; msgq = msgq->Next) { - if ((FlagsSet == (msgq->Flags & FlagsSet)) && (0 == (msgq->Flags & FlagsNotSet))) { - if (mode) - msgq->Flags = msgq->Flags | FlagsToSetRemove; - else - msgq->Flags = msgq->Flags & ~FlagsToSetRemove; - } - } -} +/* + * This code implements retrieving info from MIME header + * + * (c) majvan 2002-2004 + */ + +#include "../stdafx.h" + + //-------------------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------------------- + + // SMALL INTRO + // Mails are queued in a queue (chained list). Pointer to first mail is pointed from Account structure + // member called Mails. + // Mail queue is ended with NULL- pointered mail (NULL handle) + + //Creates new mail for plugin (calling plugin's constructor, when plugin imported to YAMN) +INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam); + +//Deletes mail for plugin (calling plugin's destructor, when plugin imported to YAMN) +INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam); + +//Loads mail data from standard storage to memory +INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam); + +//Deletes mail data from memory +INT_PTR UnloadMailDataSvc(WPARAM wParam, LPARAM); + +//Saves mail data from memory to standard storage +INT_PTR SaveMailDataSvc(WPARAM wParam, LPARAM lParam); + +//Appends second MIME mail queue to the first one +//Only finds the end of first queue and its Next memember repoints to second one +void WINAPI AppendQueueFcn(HYAMNMAIL first, HYAMNMAIL second); + +//Synchronizes two accounts +//Function finds, if there were some mails deleted from mailbox and deletes (depends on RemovedOld param) them from OldQueue +//Next finds, if there are new mails. Mails that are still on mailbox are deleted (depends on RemovedNew param) from NewQueue +//After this, OldQueue is pointer to mails that are on mailbox, but not new mails +//and NewQueue contains new mails in account +//New accounts can be then appended to account mails queue, but they have set the New flag +// +//Two mails equals if they have the same ID +// +// hPlugin- handle of plugin going to delete mails +// OldQueue- queue of mails that we found on mailbox last time, after function finishes queue contains all mails except new ones +// RemovedOld- queue of mails where to store removed mails from OldQueue, if NULL deletes mails from OldQueue +// NewQueue- queue of mails that we found on mailbox (all mails), after function finishes queue contains only new mails +// RemovedNew- queue of mails where to store removed mails from NewQueue, if NULL deletes mails from NewQueue +//So function works like: +//1. delete (or move to RemovedOld queue if RemovedOld is not NULL) all mails from OldQueue not found in NewQueue +//2. delete (or move to RemovedNew queue if RemovedNew is not NULL) all mails from NewQueue found in OldQueue +void WINAPI SynchroMessagesFcn(CAccount *Account, HYAMNMAIL *OldQueue, HYAMNMAIL *RemovedOld, HYAMNMAIL *NewQueue, HYAMNMAIL *RemovedNew); + +//Deletes messages from mail From to the end +// Account- account who owns mails +// From- first mail in queue, which is going to delete +void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From); + +//Removes message from queue, does not delete from memory +// From- queue pointer +// Which- mail to delete +// mode- nonzero if you want to decrement numbers in messages that are bigger than the one in Which mail, 0 if not +void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode); + +//Finds message in queue that has the same ID number +// From- message queue +// ID- pointer to ID +// returns pointer to found message, NULL if not found +HYAMNMAIL WINAPI FindMessageByIDFcn(HYAMNMAIL From, char *ID); + +//Translate header from text to queue of CMimeItem structures +//This means that new queue will contain all info about headers +// stream- pointer to text containing header (can be ended with zero) +// len- length of stream +// head- function fills this pointer to first header item in queue +void WINAPI TranslateHeaderFcn(char *stream, int len, struct CMimeItem **head); + +//Creates new mail queue, copying only these mails, that have set flag for deleting +// From- message queue, whose mail with given flag are duplicated +// returns new mail queue (or NULL when no mail with flag is in From queue) +//Function does not copy the whole mails, it copies only ID string. And ID is copied as string, so +//you can use this fcn only if you have your ID as pointer to char string ended with zero character +HYAMNMAIL WINAPI CreateNewDeleteQueueFcn(HYAMNMAIL From); + +//Sets/removes flags from specific mails +// From- pointer to first message +// FlagsSet- mail must have set these flags... +// FlagsNotSet- ...and must not have set these flags... +// FlagsToSetRemove- ...to set/remove these flags (see mode) +// mode- nonzero to set, else remove +void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSetRemove, int mode); + +struct CExportedFunctions MailExportedFcn[] = +{ + {YAMN_SYNCHROMIMEMSGSID, (void *)SynchroMessagesFcn}, + {YAMN_TRANSLATEHEADERID, (void *)TranslateHeaderFcn}, + {YAMN_APPENDQUEUEID, (void *)AppendQueueFcn}, + {YAMN_DELETEMIMEQUEUEID, (void *)DeleteMessagesToEndFcn}, + {YAMN_DELETEMIMEMESSAGEID, (void *)DeleteMessageFromQueueFcn}, + {YAMN_FINDMIMEMESSAGEID, (void *)FindMessageByIDFcn}, + {YAMN_CREATENEWDELETEQUEUEID, (void *)CreateNewDeleteQueueFcn}, + {YAMN_SETREMOVEQUEUEFLAGSID, (void *)SetRemoveFlagsInQueueFcn}, +}; + +struct CExportedServices MailExportedSvc[] = +{ + {MS_YAMN_CREATEACCOUNTMAIL, CreateAccountMailSvc}, + {MS_YAMN_DELETEACCOUNTMAIL, DeleteAccountMailSvc}, + {MS_YAMN_LOADMAILDATA, LoadMailDataSvc}, + {MS_YAMN_UNLOADMAILDATA, UnloadMailDataSvc}, + {MS_YAMN_SAVEMAILDATA, SaveMailDataSvc}, +}; + + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam) +{ + CAccount *Account = (CAccount *)wParam; + uint32_t MailVersion = (uint32_t)lParam; + HYAMNMAIL NewMail; + + //test if we are going to initialize members of suitable structure (structures of plugin and YAMN must match) + if (MailVersion != YAMN_MAILVERSION) + return NULL; + + if (Account->Plugin != nullptr) { + if (Account->Plugin->MailFcn->NewMailFcnPtr != nullptr) { + //Let plugin create its own structure, which can be derived from CAccount structure + if (nullptr == (NewMail = Account->Plugin->MailFcn->NewMailFcnPtr(Account, YAMN_MAILVERSION))) + return NULL; + } + else { + //We suggest plugin uses standard CAccount structure, so we create it + if (nullptr == (NewMail = new YAMNMAIL)) + //If not created successfully + return NULL; + NewMail->MailData = nullptr; + } + //Init every members of structure, used by YAMN + return (INT_PTR)NewMail; + } + return NULL; +} + +INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + HYAMNMAIL OldMail = (HYAMNMAIL)lParam; + struct CMimeItem *TH; + + if (Plugin->MailFcn != nullptr) { + if (Plugin->MailFcn->DeleteMailFcnPtr != nullptr) { + //Let plugin delete its own CMimeMsgQueue derived structure + Plugin->MailFcn->DeleteMailFcnPtr(OldMail); + return 1; + } + } + if (OldMail->MailData != nullptr) { + if (OldMail->MailData->Body != nullptr) + delete[] OldMail->MailData->Body; + if ((TH = OldMail->MailData->TranslatedHeader) != nullptr) + for (; OldMail->MailData->TranslatedHeader != nullptr;) { + TH = TH->Next; + if (OldMail->MailData->TranslatedHeader->name != nullptr) + delete[] OldMail->MailData->TranslatedHeader->name; + if (OldMail->MailData->TranslatedHeader->value != nullptr) + delete[] OldMail->MailData->TranslatedHeader->value; + delete OldMail->MailData->TranslatedHeader; + OldMail->MailData->TranslatedHeader = TH; + } + delete OldMail->MailData; + } + if (OldMail->ID != nullptr) + delete[] OldMail->ID; + + delete OldMail; //consider mail as standard HYAMNMAIL, not initialized before and use its own destructor + return 1; +} + + +void WINAPI AppendQueueFcn(HYAMNMAIL first, HYAMNMAIL second) +{ + HYAMNMAIL Finder = first; + while (Finder->Next != nullptr) Finder = Finder->Next; + Finder->Next = second; +} + +INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam) +{ + HYAMNMAIL Mail = (HYAMNMAIL)wParam; + uint32_t MailVersion = (uint32_t)lParam; + + if (MailVersion != YAMN_MAILDATAVERSION) + return NULL; + + // now we have all data to memory persisting, so no loading is needed + return (INT_PTR)Mail->MailData; +} + +INT_PTR UnloadMailDataSvc(WPARAM, LPARAM) +{ + return 1; +} + +INT_PTR SaveMailDataSvc(WPARAM, LPARAM lParam) +{ + uint32_t MailVersion = (uint32_t)lParam; + + if (MailVersion != YAMN_MAILDATAVERSION) + return (INT_PTR)-1; + + // now we have all data to memory persisting, so no saving is needed + return (INT_PTR)0; +} + +void WINAPI SynchroMessagesFcn(CAccount *Account, HYAMNMAIL *OldQueue, HYAMNMAIL *RemovedOld, HYAMNMAIL *NewQueue, HYAMNMAIL *RemovedNew) +//deletes messages from new queue, if they are old +//it also deletes messages from old queue, if they are not in mailbox anymore +//"YAMN_MSG_DELETED" messages in old queue remain in old queue (are never removed, although they are not in new queue) +//"YAMN_MSG_DELETED" messages in new queue remain in new queue (are never removed, although they can be in old queue) +{ + HYAMNMAIL Finder, FinderPrev; + HYAMNMAIL Parser, ParserPrev; + HYAMNMAIL RemovedOldParser = nullptr; + HYAMNMAIL RemovedNewParser = nullptr; + if (RemovedOld != nullptr) *RemovedOld = nullptr; + if (RemovedNew != nullptr) *RemovedNew = nullptr; + + for (FinderPrev = nullptr, Finder = *OldQueue; Finder != nullptr;) { + if (Finder->Flags & YAMN_MSG_DELETED) //if old queue contains deleted mail + { + FinderPrev = Finder; + Finder = Finder->Next; //get next message in old queue for testing + continue; + } + for (ParserPrev = nullptr, Parser = *NewQueue; Parser != nullptr; ParserPrev = Parser, Parser = Parser->Next) { + if (Parser->Flags & YAMN_MSG_DELETED) + continue; + + if (Parser->ID == nullptr) //simply ignore the message, that has not filled its ID + continue; + + if (0 == mir_strcmp(Parser->ID, Finder->ID)) //search for equal message in new queue + break; + } + if (Parser != nullptr) //found equal message in new queue + { + if (Parser == *NewQueue) + *NewQueue = (*NewQueue)->Next; + else + ParserPrev->Next = Parser->Next; + Finder->Number = Parser->Number; //rewrite the number of current message in old queue + + if (RemovedNew == nullptr) //delete from new queue + DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Parser); + else //or move to RemovedNew + { + if (RemovedNewParser == nullptr) //if it is first mail removed from NewQueue + *RemovedNew = Parser; //set RemovedNew queue to point to first message in removed queue + else + RemovedNewParser->Next = Parser; //else don't forget to show to next message in RemovedNew queue + RemovedNewParser = Parser; //follow RemovedNew queue + RemovedNewParser->Next = nullptr; + } + FinderPrev = Finder; + Finder = Finder->Next; //get next message in old queue for testing + } + else //a message was already deleted from mailbox + { + if (Finder == *OldQueue) //if we are at the first item in OldQueue + { + *OldQueue = (*OldQueue)->Next; //set OldQueue to next item + if (RemovedOld == nullptr) //delete from old queue + DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Finder); + else //or move to RemovedOld + { + if (RemovedOldParser == nullptr) //if it is first mail removed from OldQueue + *RemovedOld = Finder; //set RemovedOld queue to point to first message in removed queue + else + RemovedOldParser->Next = Finder; //else don't forget to show to next message in RemovedNew queue + RemovedOldParser = Finder; //follow RemovedOld queue + RemovedOldParser->Next = nullptr; + } + Finder = *OldQueue; + } + else { + FinderPrev->Next = Finder->Next; + if (RemovedOld == nullptr) //delete from old queue + DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Finder); + else //or move to RemovedOld + { + if (RemovedOldParser == nullptr) //if it is first mail removed from OldQueue + *RemovedOld = Finder; //set RemovedOld queue to point to first message in removed queue + else + RemovedOldParser->Next = Finder; //else don't forget to show to next message in RemovedNew queue + RemovedOldParser = Finder; //follow RemovedOld queue + RemovedOldParser->Next = nullptr; + } + Finder = FinderPrev->Next; + } + } + } +} + +void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From) +{ + HYAMNMAIL Temp; + while (From != nullptr) { + Temp = From; + From = From->Next; + DeleteAccountMailSvc((WPARAM)Account->Plugin, (LPARAM)Temp); + } +} + +void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode = 0) +{ + uint32_t Number = Which->Number; + HYAMNMAIL Parser; + + if (*From == Which) { + Parser = Which->Next; + *From = Parser; + } + else { + for (Parser = *From; Which != Parser->Next; Parser = Parser->Next) + if (mode && (Parser->Number > Number)) Parser->Number--; + if (mode && (Parser->Number > Number)) Parser->Number--; + Parser->Next = Parser->Next->Next; + Parser = Which->Next; + } + if (mode) + for (; Parser != nullptr; Parser = Parser->Next) + if (Parser->Number > Number) Parser->Number--; +} + +void DeleteMessagesFromQueue(HYAMNMAIL *From, HYAMNMAIL Which, int mode = 0) +{ + HYAMNMAIL Parser; + + for (Parser = Which; Parser != nullptr; Parser = Parser->Next) + DeleteMessageFromQueueFcn(From, Parser, mode); +} + +HYAMNMAIL WINAPI FindMessageByIDFcn(HYAMNMAIL From, char *ID) +{ + HYAMNMAIL Browser; + + for (Browser = From; Browser != nullptr; Browser = Browser->Next) + if (0 == mir_strcmp(Browser->ID, ID)) + break; + return Browser; +} + +void WINAPI TranslateHeaderFcn(char *stream, int len, struct CMimeItem **head) +{ + try { + char *finder = stream; + char *prev1, *prev2, *prev3; + struct CMimeItem *Item = nullptr; + + while (finder <= (stream + len)) { + while (ENDLINEWS(finder)) finder++; + + //at the start of line + if (DOTLINE(finder + 1)) //at the end of stream + break; + + prev1 = finder; + + while (*finder != ':' && !EOS(finder)) finder++; + if (!EOS(finder)) + prev2 = finder++; + else + break; + + while (WS(finder) && !EOS(finder)) finder++; + if (!EOS(finder)) + prev3 = finder; + else + break; + + do { + if (ENDLINEWS(finder)) finder += 2; //after endline information continues + while (!ENDLINE(finder) && !EOS(finder)) finder++; + } while (ENDLINEWS(finder)); + + if (Item != nullptr) { + if (nullptr == (Item->Next = new struct CMimeItem)) + break; + Item = Item->Next; + } + else { + Item = new CMimeItem; + *head = Item; + } + + Item->Next = nullptr; + Item->name = new char[prev2 - prev1 + 1]; + mir_strncpy(Item->name, prev1, prev2 - prev1 + 1); + Item->value = new char[finder - prev3 + 1]; + mir_strncpy(Item->value, prev3, finder - prev3 + 1); + + if (EOS(finder)) + break; + finder++; + if (ENDLINE(finder)) { + finder++; + if (ENDLINE(finder)) { + // end of headers. message body begins + finder++; + if (ENDLINE(finder))finder++; + prev1 = finder; + while (!DOTLINE(finder + 1))finder++; + if (ENDLINE(finder))finder--; + prev2 = finder; + if (prev2 > prev1) { // yes, we have body + if (nullptr == (Item->Next = new struct CMimeItem)) break; // Cant create new item?! + Item = Item->Next; + Item->Next = nullptr;//just in case; + Item->name = new char[5]; strncpy(Item->name, "Body", 5); + Item->value = new char[prev2 - prev1]; + mir_strncpy(Item->value, prev1, prev2 - prev1 - 1); + } + break; // there is nothing else + } + } + } + } + catch (...) { + MessageBoxA(nullptr, "Translate header error", "", 0); + } +} + +HYAMNMAIL WINAPI CreateNewDeleteQueueFcn(HYAMNMAIL From) +{ + HYAMNMAIL FirstMail, Browser = nullptr; + + for (FirstMail = nullptr; From != nullptr; From = From->Next) { + if ((From->Flags & (YAMN_MSG_USERDELETE | YAMN_MSG_AUTODELETE)) && !(From->Flags & YAMN_MSG_DELETED)) { + if (FirstMail == nullptr) { + FirstMail = Browser = new YAMNMAIL; + if (FirstMail == nullptr) + break; + } + else { + Browser->Next = new YAMNMAIL; + Browser = Browser->Next; + } + Browser->ID = new char[mir_strlen(From->ID) + 1]; + mir_strcpy(Browser->ID, From->ID); + Browser->Number = From->Number; + Browser->Flags = From->Flags; + } + } + return FirstMail; +} + +void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSetRemove, int mode) +{ + HYAMNMAIL msgq; + + for (msgq = (HYAMNMAIL)From; msgq != nullptr; msgq = msgq->Next) { + if ((FlagsSet == (msgq->Flags & FlagsSet)) && (0 == (msgq->Flags & FlagsNotSet))) { + if (mode) + msgq->Flags = msgq->Flags | FlagsToSetRemove; + else + msgq->Flags = msgq->Flags & ~FlagsToSetRemove; + } + } +} diff --git a/protocols/YAMN/src/mails/mime.cpp b/protocols/YAMN/src/mails/mime.cpp index 69099af808..2a2e7b4665 100644 --- a/protocols/YAMN/src/mails/mime.cpp +++ b/protocols/YAMN/src/mails/mime.cpp @@ -1,695 +1,695 @@ -/* - * This code implements retrieving info from MIME header - * - * (c) majvan 2002-2004 - */ - -#include "../stdafx.h" - - //-------------------------------------------------------------------------------------------------- - - //Copies one string to another - // srcstart- source string - // srcend- address to the end of source string - // dest- pointer that stores new allocated string that contains copy of source string - // mode- MIME_PLAIN or MIME_MAIL (MIME_MAIL deletes '"' characters (or '<' and '>') if they are at start and end of source string -void CopyToHeader(char *srcstart, char *srcend, char **dest, int mode); - -//Extracts email address (finds nick name and mail and then stores them to strings) -// finder- source string -// storeto- pointer that receives address of mail string -// storetonick- pointer that receives address of nickname -void ExtractAddressFromLine(char *finder, char **storeto, char **storetonick); - -//Extracts simple text from string -// finder- source string -// storeto- pointer that receives address of string -void ExtractStringFromLine(char *finder, char **storeto); - -//Extracts some item from content-type string -//Example: ContentType string: "TEXT/PLAIN; charset=US-ASCII", item:"charset=", returns: "US-ASCII" -// ContetType- content-type string -// value- string item -// returns extracted string (or NULL when not found) -char *ExtractFromContentType(char *ContentType, char *value); - -//Extracts info from header text into header members -//Note that this function as well as struct CShortHeadwer can be always changed, because there are many items to extract -//(e.g. the X-Priority and Importance and so on) -// items- translated header (see TranslateHeaderFcn) -// head- header to be filled with values extracted from items -void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head); - -//Extracts header to mail using ExtractShortHeader fcn. -// items- translated header (see TranslateHeaderFcn) -// CP- codepage used when no default found -// head- header to be filled with values extracted from items, in unicode (wide char) -void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head); - -//Deletes items in CShortHeader structure -// head- structure whose items are deleted -void DeleteShortHeaderContent(struct CShortHeader *head); - -//Deletes list of YAMN_MIMENAMES structures -// Names- pointer to first item of list -void DeleteNames(CMimeNames *Names); - -//Deletes list of YAMN_MIMESHORTNAMES structures -// Names- pointer to first item of list -void DeleteShortNames(CShortNames *Names); - -//Makes a string lowercase -// string- string to be lowercased -void inline ToLower(char *string); - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -void CopyToHeader(char *srcstart, char *srcend, char **dest, int mode) -{ - char *dst; - - if (dest == nullptr) - return; - if (srcstart >= srcend) - return; - - if ((mode == MIME_MAIL) && (((*srcstart == '"') && (*(srcend - 1) == '"')) || ((*srcstart == '<') && (*(srcend - 1) == '>')))) { - srcstart++; - srcend--; - } - - if (srcstart >= srcend) - return; - - if (nullptr != *dest) - delete[] * dest; - if (nullptr == (*dest = new char[srcend - srcstart + 1])) - return; - - dst = *dest; - - for (; srcstart < srcend; dst++, srcstart++) { - if (ENDLINE(srcstart)) { - while (ENDLINE(srcstart) || WS(srcstart)) srcstart++; - *dst = ' '; - srcstart--; //because at the end of "for loop" we increment srcstart - } - else - *dst = *srcstart; - } - *dst = 0; -} - -void ExtractAddressFromLine(char *finder, char **storeto, char **storetonick) -{ - if (finder == nullptr) { - *storeto = *storetonick = nullptr; - return; - } - while (WS(finder)) finder++; - if ((*finder) != '<') { - char *finderend = finder + 1; - do { - if (ENDLINEWS(finderend)) //after endline information continues - finderend += 2; - while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; //seek to the end of line or to the end of string - } while (ENDLINEWS(finderend)); - finderend--; - while (WS(finderend) || ENDLINE(finderend)) finderend--; //find the end of text, no whitespace - if (*finderend != '>') //not '>' at the end of line - CopyToHeader(finder, finderend + 1, storeto, MIME_MAIL); - else //at the end of line, there's '>' - { - char *finder2 = finderend; - while ((*finder2 != '<') && (finder2 > finder)) finder2--; //go to matching '<' or to the start - CopyToHeader(finder2, finderend + 1, storeto, MIME_MAIL); - if (*finder2 == '<') //if we found '<', the rest copy as from nick - { - finder2--; - while (WS(finder2) || ENDLINE(finder2)) finder2--; //parse whitespace - CopyToHeader(finder, finder2 + 1, storetonick, MIME_MAIL); //and store nickname - } - } - } - else { - char *finderend = finder + 1; - do { - if (ENDLINEWS(finderend)) //after endline information continues - finderend += 2; - while (!ENDLINE(finderend) && (*finderend != '>') && !EOS(finderend)) finderend++; //seek to the matching < or to the end of line or to the end of string - } while (ENDLINEWS(finderend)); - CopyToHeader(finder, finderend + 1, storeto, MIME_MAIL); //go to first '>' or to the end and copy - finder = finderend + 1; - while (WS(finder)) finder++; //parse whitespace - if (!ENDLINE(finder) && !EOS(finder)) //if there are chars yet, it's nick - { - finderend = finder + 1; - while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; //seek to the end of line or to the end of string - finderend--; - while (WS(finderend)) finderend--; //find the end of line, no whitespace - CopyToHeader(finder, finderend + 1, storetonick, MIME_MAIL); - } - } -} - -void ExtractStringFromLine(char *finder, char **storeto) -{ - if (finder == nullptr) { - *storeto = nullptr; - return; - } - while (WS(finder)) finder++; - char *finderend = finder; - - do { - if (ENDLINEWS(finderend)) finderend++; //after endline information continues - while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; - } while (ENDLINEWS(finderend)); - finderend--; - while (WS(finderend)) finderend--; //find the end of line, no whitespace - CopyToHeader(finder, finderend + 1, storeto, MIME_PLAIN); -} - -char *ExtractFromContentType(char *ContentType, char *value) -{ - char *lowered = _strdup(ContentType); - ToLower(lowered); - char *finder = strstr(lowered, value); - if (finder == nullptr) { - free(lowered); - return nullptr; - } - finder = finder - lowered + ContentType; - free(lowered); - - char *temp, *copier; - char *CopiedString; - - temp = finder - 1; - while ((temp > ContentType) && WS(temp)) temp--; //now we have to find, if the word "Charset=" is located after ';' like "; Charset=" - if (*temp != ';' && !ENDLINE(temp) && temp != ContentType) - return nullptr; - finder = finder + mir_strlen(value); //jump over value string - - while (WS(finder)) finder++; //jump over whitespaces - temp = finder; - while (*temp != 0 && *temp != ';') temp++; //jump to the end of setting (to the next ;) - temp--; - while (WS(temp)) temp--; //remove whitespaces from the end - if (*finder == '\"') { //remove heading and tailing quotes - finder++; - if (*temp == '\"') temp--; - } - if (nullptr == (CopiedString = new char[++temp - finder + 1])) - return nullptr; - for (copier = CopiedString; finder != temp; *copier++ = *finder++); //copy string - *copier = 0; //and end it with zero character - - return CopiedString; -} - -void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head) -{ - for (; items != nullptr; items = items->Next) { - //at the start of line - //MessageBox(NULL,items->value,items->name,0); - if (0 == _strnicmp(items->name, "From", 4)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractAddressFromLine(items->value, &head->From, &head->FromNick); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "Return-Path", 11)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractAddressFromLine(items->value, &head->ReturnPath, &head->ReturnPathNick); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "Subject", 7)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractStringFromLine(items->value, &head->Subject); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "Body", 4)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractStringFromLine(items->value, &head->Body); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "Date", 4)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractStringFromLine(items->value, &head->Date); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "Content-Type", 12)) { - if (items->value == nullptr) - continue; - - char *ContentType = nullptr, *CharSetStr; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - ExtractStringFromLine(items->value, &ContentType); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - ToLower(ContentType); - if (nullptr != (CharSetStr = ExtractFromContentType(ContentType, "charset="))) { - head->CP = GetCharsetFromString(CharSetStr, mir_strlen(CharSetStr)); - delete[] CharSetStr; - } - delete[] ContentType; - } - else if (0 == _strnicmp(items->name, "Importance", 10)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - if (head->Priority != -1) { - if (0 == strncmp(items->value, "low", 3)) - head->Priority = 5; - else if (0 == strncmp(items->value, "normal", 6)) - head->Priority = 3; - else if (0 == strncmp(items->value, "high", 4)) - head->Priority = 1; - } - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - else if (0 == _strnicmp(items->name, "X-Priority", 10)) { - if (items->value == nullptr) - continue; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, ""); - #endif - if ((*items->value >= '1') && (*items->value <= '5')) - head->Priority = *items->value - '0'; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - } - - } -} - -void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head) -{ - struct CShortHeader ShortHeader; - - memset(&ShortHeader, 0, sizeof(struct CShortHeader)); - ShortHeader.Priority = ShortHeader.CP = -1; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - ExtractShortHeader(items, &ShortHeader); - - head->Priority = ShortHeader.Priority == -1 ? 3 : ShortHeader.Priority; - CP = ShortHeader.CP == -1 ? CP : ShortHeader.CP; - #ifdef DEBUG_DECODE - if (NULL != ShortHeader.From) - DebugLog(DecodeFile, "%s%s%s%s%s%s\n"); - DebugLog(DecodeFile, "\n"); - #endif - - ConvertCodedStringToUnicode(ShortHeader.From, &head->From, CP, MIME_PLAIN); - - #ifdef DEBUG_DECODE - if (NULL != head->From) - DebugLogW(DecodeFile, L"%s\n", head->From); - #endif - ConvertCodedStringToUnicode(ShortHeader.FromNick, &head->FromNick, CP, MIME_MAIL); - #ifdef DEBUG_DECODE - if (NULL != head->FromNick) - DebugLogW(DecodeFile, L"%s\n", head->FromNick); - #endif - ConvertCodedStringToUnicode(ShortHeader.ReturnPath, &head->ReturnPath, CP, MIME_PLAIN); - #ifdef DEBUG_DECODE - if (NULL != head->ReturnPath) - DebugLogW(DecodeFile, L"%s\n", head->ReturnPath); - #endif - ConvertCodedStringToUnicode(ShortHeader.ReturnPathNick, &head->ReturnPathNick, CP, MIME_MAIL); - #ifdef DEBUG_DECODE - if (NULL != head->ReturnPathNick) - DebugLogW(DecodeFile, L"%s\n", head->ReturnPathNick); - #endif - ConvertCodedStringToUnicode(ShortHeader.Subject, &head->Subject, CP, MIME_PLAIN); - #ifdef DEBUG_DECODE - if (NULL != head->Subject) - DebugLogW(DecodeFile, L"%s\n", head->Subject); - #endif - ConvertCodedStringToUnicode(ShortHeader.Date, &head->Date, CP, MIME_PLAIN); - #ifdef DEBUG_DECODE - if (NULL != head->Date) - DebugLogW(DecodeFile, L"%s\n", head->Date); - #endif - - ConvertCodedStringToUnicode(ShortHeader.Body, &head->Body, CP, MIME_PLAIN); - #ifdef DEBUG_DECODE - if (NULL != head->Body) - DebugLogW(DecodeFile, L"%s\n", head->Body); - #endif - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - - DeleteShortHeaderContent(&ShortHeader); - - // head->From=L"Frommmm"; - // head->Subject=L"Subject"; - return; -} - -void DeleteShortHeaderContent(struct CShortHeader *head) -{ - if (head->From != nullptr) delete[] head->From; - if (head->FromNick != nullptr) delete[] head->FromNick; - if (head->ReturnPath != nullptr) delete[] head->ReturnPath; - if (head->ReturnPathNick != nullptr) delete[] head->ReturnPathNick; - if (head->Subject != nullptr) delete[] head->Subject; - if (head->Date != nullptr) delete[] head->Date; - if (head->To != nullptr) DeleteShortNames(head->To); - if (head->Cc != nullptr) DeleteShortNames(head->Cc); - if (head->Bcc != nullptr) DeleteShortNames(head->Bcc); - if (head->Body != nullptr) delete[] head->Body; -} - -void DeleteHeaderContent(struct CHeader *head) -{ - if (head->From != nullptr) delete[] head->From; - if (head->FromNick != nullptr) delete[] head->FromNick; - if (head->ReturnPath != nullptr) delete[] head->ReturnPath; - if (head->ReturnPathNick != nullptr) delete[] head->ReturnPathNick; - if (head->Subject != nullptr) delete[] head->Subject; - if (head->Date != nullptr) delete[] head->Date; - if (head->Body != nullptr) delete[] head->Body; - if (head->To != nullptr) DeleteNames(head->To); - if (head->Cc != nullptr) DeleteNames(head->Cc); - if (head->Bcc != nullptr) DeleteNames(head->Bcc); -} - -void DeleteNames(CMimeNames *Names) -{ - CMimeNames *Parser = Names; - for (; Parser != nullptr; Parser = Parser->Next) { - if (Parser->Value != nullptr) - delete[] Parser->Value; - if (Parser->ValueNick != nullptr) - delete[] Parser->ValueNick; - - CMimeNames *Old = Parser; - Parser = Parser->Next; - delete Old; - } -} - -void DeleteShortNames(CShortNames *Names) -{ - CShortNames *Parser = Names; - for (; Parser != nullptr; Parser = Parser->Next) { - if (Parser->Value != nullptr) - delete[] Parser->Value; - if (Parser->ValueNick != nullptr) - delete[] Parser->ValueNick; - - CShortNames *Old = Parser; - Parser = Parser->Next; - delete Old; - } -} - - -void inline ToLower(char *string) -{ - for (; *string != 0; string++) - if (*string >= 'A' && *string <= 'Z') *string = *string - 'A' + 'a'; -} - -#define TE_UNKNOWN -#define TE_QUOTEDPRINTABLE 1 -#define TE_BASE64 2 -struct APartDataType -{ - char *Src;//Input - char *ContType; - int CodePage; - char *TransEnc; - uint8_t TransEncType; //TE_something - char *body; - int bodyLen; - wchar_t *wBody; -}; - - -void ParseAPart(APartDataType *data) -{ - size_t len = mir_strlen(data->Src); - try { - char *finder = data->Src; - char *prev1, *prev2, *prev3; - - while (finder <= (data->Src + len)) { - while (ENDLINEWS(finder)) finder++; - - //at the start of line - if (finder > data->Src) { - if (*(finder - 2) == '\r' || *(finder - 2) == '\n') - *(finder - 2) = 0; - if (*(finder - 1) == '\r' || *(finder - 1) == '\n') - *(finder - 1) = 0; - } - prev1 = finder; - - while (*finder != ':' && !EOS(finder) && !ENDLINE(finder)) finder++; - if (ENDLINE(finder) || EOS(finder)) { - // no ":" in the line? here the body begins; - data->body = prev1; - break; - } - prev2 = finder++; - - while (WS(finder) && !EOS(finder)) finder++; - if (!EOS(finder)) - prev3 = finder; - else - break; - - do { - if (ENDLINEWS(finder)) finder += 2; //after endline information continues - while (!ENDLINE(finder) && !EOS(finder)) finder++; - } while (ENDLINEWS(finder)); - - if (!_strnicmp(prev1, "Content-type", prev2 - prev1)) { - data->ContType = prev3; - } - else if (!_strnicmp(prev1, "Content-Transfer-Encoding", prev2 - prev1)) { - data->TransEnc = prev3; - } - - if (EOS(finder)) - break; - finder++; - if (ENDLINE(finder)) { - finder++; - if (ENDLINE(finder)) { - // end of headers. message body begins - if (finder > data->Src) { - if (*(finder - 2) == '\r' || *(finder - 2) == '\n') - *(finder - 2) = 0; - if (*(finder - 1) == '\r' || *(finder - 1) == '\n') - *(finder - 1) = 0; - } - finder++; - if (ENDLINE(finder))finder++; - prev1 = finder; - while (!EOS(finder + 1))finder++; - if (ENDLINE(finder))finder--; - prev2 = finder; - if (prev2 > prev1) { // yes, we have body - data->body = prev1; - } - break; // there is nothing else - } - } - } - } - catch (...) { - MessageBox(nullptr, TranslateT("Translate header error"), L"", 0); - } - if (data->body) data->bodyLen = (int)mir_strlen(data->body); -} - -//from decode.cpp -int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); -int DecodeBase64(char *Src, char *Dst, int DstLen); -int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); - -wchar_t *ParseMultipartBody(char *src, char *bond) -{ - char *srcback = _strdup(src); - size_t sizebond = mir_strlen(bond); - int numparts = 1; - int i; - char *courbond = srcback; - wchar_t *dest; - for (; (courbond = strstr(courbond, bond)); numparts++, courbond += sizebond); - APartDataType *partData = new APartDataType[numparts]; - memset(partData, 0, sizeof(APartDataType) * numparts); - partData[0].Src = courbond = srcback; - for (i = 1; (courbond = strstr(courbond, bond)); i++, courbond += sizebond) { - *(courbond - 2) = 0; - partData[i].Src = courbond + sizebond; - while (ENDLINE(partData[i].Src)) partData[i].Src++; - } - size_t resultSize = 0; - for (i = 0; i < numparts; i++) { - ParseAPart(&partData[i]); - if (partData[i].body) { - if (partData[i].TransEnc) { - if (!_stricmp(partData[i].TransEnc, "base64")) partData[i].TransEncType = TE_BASE64; - else if (!_stricmp(partData[i].TransEnc, "quoted-printable"))partData[i].TransEncType = TE_QUOTEDPRINTABLE; - } - if (partData[i].ContType) { - char *CharSetStr; - if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "charset="))) { - partData[i].CodePage = GetCharsetFromString(CharSetStr, mir_strlen(CharSetStr)); - delete[] CharSetStr; - } - } - if (partData[i].ContType && !_strnicmp(partData[i].ContType, "text", 4)) { - char *localBody = nullptr; - switch (partData[i].TransEncType) { - case TE_BASE64: - { - int size = partData[i].bodyLen * 3 / 4 + 5; - localBody = new char[size + 1]; - DecodeBase64(partData[i].body, localBody, size); - }break; - case TE_QUOTEDPRINTABLE: - { - int size = partData[i].bodyLen + 2; - localBody = new char[size + 1]; - DecodeQuotedPrintable(partData[i].body, localBody, size, FALSE); - }break; - } - ConvertStringToUnicode(localBody ? localBody : partData[i].body, partData[i].CodePage, &partData[i].wBody); - if (localBody) delete[] localBody; - } - else if (partData[i].ContType && !_strnicmp(partData[i].ContType, "multipart/", 10)) { - //Multipart in mulitipart recursive? should be SPAM. Ah well - char *bondary = nullptr; - if (nullptr != (bondary = ExtractFromContentType(partData[i].ContType, "boundary="))) { - partData[i].wBody = ParseMultipartBody(partData[i].body, bondary); - delete[] bondary; - } - else goto FailBackRaw; //multipart with no boundary? badly formatted messages. - } - else { -FailBackRaw: - ConvertStringToUnicode(partData[i].body, partData[i].CodePage, &partData[i].wBody); - } - resultSize += mir_wstrlen(partData[i].wBody); - }// if (partData[i].body) - resultSize += 100 + 4 + 3; //cr+nl+100+ 3*bullet - } - dest = new wchar_t[resultSize + 1]; - size_t destpos = 0; - for (i = 0; i < numparts; i++) { - if (i) { // part before first boudary should not have headers - char infoline[1024]; size_t linesize = 0; - mir_snprintf(infoline, "%s %d", Translate("Part"), i); - linesize = mir_strlen(infoline); - if (partData[i].TransEnc) { - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].TransEnc); - linesize = mir_strlen(infoline); - } - if (partData[i].ContType) { - char *CharSetStr = strchr(partData[i].ContType, ';'); - if (CharSetStr) { - CharSetStr[0] = 0; - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].ContType); - linesize = mir_strlen(infoline); - partData[i].ContType = CharSetStr + 1; - if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "charset="))) { - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", CharSetStr); - linesize = mir_strlen(infoline); - delete[] CharSetStr; - } - if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "name="))) { - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; \"%s\"", CharSetStr); - linesize = mir_strlen(infoline); - delete[] CharSetStr; - } - } - else { - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].ContType); - linesize = mir_strlen(infoline); - } - } - mir_snprintf(infoline + linesize, _countof(infoline) - linesize, ".\r\n"); - { - wchar_t *temp = nullptr; - dest[destpos] = dest[destpos + 1] = dest[destpos + 2] = 0x2022; // bullet; - destpos += 3; - ConvertStringToUnicode(infoline, CP_ACP, &temp); - size_t wsize = mir_wstrlen(temp); - mir_wstrcpy(&dest[destpos], temp); - destpos += wsize; - delete[] temp; - } - } // if (i) - - if (partData[i].wBody) { - size_t wsize = mir_wstrlen(partData[i].wBody); - mir_wstrcpy(&dest[destpos], partData[i].wBody); - destpos += wsize; - delete[] partData[i].wBody; - } - } - - free(srcback); - delete[] partData; - dest[resultSize] = 0;//just in case - return dest; -} +/* + * This code implements retrieving info from MIME header + * + * (c) majvan 2002-2004 + */ + +#include "../stdafx.h" + + //-------------------------------------------------------------------------------------------------- + + //Copies one string to another + // srcstart- source string + // srcend- address to the end of source string + // dest- pointer that stores new allocated string that contains copy of source string + // mode- MIME_PLAIN or MIME_MAIL (MIME_MAIL deletes '"' characters (or '<' and '>') if they are at start and end of source string +void CopyToHeader(char *srcstart, char *srcend, char **dest, int mode); + +//Extracts email address (finds nick name and mail and then stores them to strings) +// finder- source string +// storeto- pointer that receives address of mail string +// storetonick- pointer that receives address of nickname +void ExtractAddressFromLine(char *finder, char **storeto, char **storetonick); + +//Extracts simple text from string +// finder- source string +// storeto- pointer that receives address of string +void ExtractStringFromLine(char *finder, char **storeto); + +//Extracts some item from content-type string +//Example: ContentType string: "TEXT/PLAIN; charset=US-ASCII", item:"charset=", returns: "US-ASCII" +// ContetType- content-type string +// value- string item +// returns extracted string (or NULL when not found) +char *ExtractFromContentType(char *ContentType, char *value); + +//Extracts info from header text into header members +//Note that this function as well as struct CShortHeadwer can be always changed, because there are many items to extract +//(e.g. the X-Priority and Importance and so on) +// items- translated header (see TranslateHeaderFcn) +// head- header to be filled with values extracted from items +void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head); + +//Extracts header to mail using ExtractShortHeader fcn. +// items- translated header (see TranslateHeaderFcn) +// CP- codepage used when no default found +// head- header to be filled with values extracted from items, in unicode (wide char) +void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head); + +//Deletes items in CShortHeader structure +// head- structure whose items are deleted +void DeleteShortHeaderContent(struct CShortHeader *head); + +//Deletes list of YAMN_MIMENAMES structures +// Names- pointer to first item of list +void DeleteNames(CMimeNames *Names); + +//Deletes list of YAMN_MIMESHORTNAMES structures +// Names- pointer to first item of list +void DeleteShortNames(CShortNames *Names); + +//Makes a string lowercase +// string- string to be lowercased +void inline ToLower(char *string); + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +void CopyToHeader(char *srcstart, char *srcend, char **dest, int mode) +{ + char *dst; + + if (dest == nullptr) + return; + if (srcstart >= srcend) + return; + + if ((mode == MIME_MAIL) && (((*srcstart == '"') && (*(srcend - 1) == '"')) || ((*srcstart == '<') && (*(srcend - 1) == '>')))) { + srcstart++; + srcend--; + } + + if (srcstart >= srcend) + return; + + if (nullptr != *dest) + delete[] * dest; + if (nullptr == (*dest = new char[srcend - srcstart + 1])) + return; + + dst = *dest; + + for (; srcstart < srcend; dst++, srcstart++) { + if (ENDLINE(srcstart)) { + while (ENDLINE(srcstart) || WS(srcstart)) srcstart++; + *dst = ' '; + srcstart--; //because at the end of "for loop" we increment srcstart + } + else + *dst = *srcstart; + } + *dst = 0; +} + +void ExtractAddressFromLine(char *finder, char **storeto, char **storetonick) +{ + if (finder == nullptr) { + *storeto = *storetonick = nullptr; + return; + } + while (WS(finder)) finder++; + if ((*finder) != '<') { + char *finderend = finder + 1; + do { + if (ENDLINEWS(finderend)) //after endline information continues + finderend += 2; + while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; //seek to the end of line or to the end of string + } while (ENDLINEWS(finderend)); + finderend--; + while (WS(finderend) || ENDLINE(finderend)) finderend--; //find the end of text, no whitespace + if (*finderend != '>') //not '>' at the end of line + CopyToHeader(finder, finderend + 1, storeto, MIME_MAIL); + else //at the end of line, there's '>' + { + char *finder2 = finderend; + while ((*finder2 != '<') && (finder2 > finder)) finder2--; //go to matching '<' or to the start + CopyToHeader(finder2, finderend + 1, storeto, MIME_MAIL); + if (*finder2 == '<') //if we found '<', the rest copy as from nick + { + finder2--; + while (WS(finder2) || ENDLINE(finder2)) finder2--; //parse whitespace + CopyToHeader(finder, finder2 + 1, storetonick, MIME_MAIL); //and store nickname + } + } + } + else { + char *finderend = finder + 1; + do { + if (ENDLINEWS(finderend)) //after endline information continues + finderend += 2; + while (!ENDLINE(finderend) && (*finderend != '>') && !EOS(finderend)) finderend++; //seek to the matching < or to the end of line or to the end of string + } while (ENDLINEWS(finderend)); + CopyToHeader(finder, finderend + 1, storeto, MIME_MAIL); //go to first '>' or to the end and copy + finder = finderend + 1; + while (WS(finder)) finder++; //parse whitespace + if (!ENDLINE(finder) && !EOS(finder)) //if there are chars yet, it's nick + { + finderend = finder + 1; + while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; //seek to the end of line or to the end of string + finderend--; + while (WS(finderend)) finderend--; //find the end of line, no whitespace + CopyToHeader(finder, finderend + 1, storetonick, MIME_MAIL); + } + } +} + +void ExtractStringFromLine(char *finder, char **storeto) +{ + if (finder == nullptr) { + *storeto = nullptr; + return; + } + while (WS(finder)) finder++; + char *finderend = finder; + + do { + if (ENDLINEWS(finderend)) finderend++; //after endline information continues + while (!ENDLINE(finderend) && !EOS(finderend)) finderend++; + } while (ENDLINEWS(finderend)); + finderend--; + while (WS(finderend)) finderend--; //find the end of line, no whitespace + CopyToHeader(finder, finderend + 1, storeto, MIME_PLAIN); +} + +char *ExtractFromContentType(char *ContentType, char *value) +{ + char *lowered = _strdup(ContentType); + ToLower(lowered); + char *finder = strstr(lowered, value); + if (finder == nullptr) { + free(lowered); + return nullptr; + } + finder = finder - lowered + ContentType; + free(lowered); + + char *temp, *copier; + char *CopiedString; + + temp = finder - 1; + while ((temp > ContentType) && WS(temp)) temp--; //now we have to find, if the word "Charset=" is located after ';' like "; Charset=" + if (*temp != ';' && !ENDLINE(temp) && temp != ContentType) + return nullptr; + finder = finder + mir_strlen(value); //jump over value string + + while (WS(finder)) finder++; //jump over whitespaces + temp = finder; + while (*temp != 0 && *temp != ';') temp++; //jump to the end of setting (to the next ;) + temp--; + while (WS(temp)) temp--; //remove whitespaces from the end + if (*finder == '\"') { //remove heading and tailing quotes + finder++; + if (*temp == '\"') temp--; + } + if (nullptr == (CopiedString = new char[++temp - finder + 1])) + return nullptr; + for (copier = CopiedString; finder != temp; *copier++ = *finder++); //copy string + *copier = 0; //and end it with zero character + + return CopiedString; +} + +void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head) +{ + for (; items != nullptr; items = items->Next) { + //at the start of line + //MessageBox(NULL,items->value,items->name,0); + if (0 == _strnicmp(items->name, "From", 4)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractAddressFromLine(items->value, &head->From, &head->FromNick); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "Return-Path", 11)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractAddressFromLine(items->value, &head->ReturnPath, &head->ReturnPathNick); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "Subject", 7)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractStringFromLine(items->value, &head->Subject); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "Body", 4)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractStringFromLine(items->value, &head->Body); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "Date", 4)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractStringFromLine(items->value, &head->Date); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "Content-Type", 12)) { + if (items->value == nullptr) + continue; + + char *ContentType = nullptr, *CharSetStr; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + ExtractStringFromLine(items->value, &ContentType); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + ToLower(ContentType); + if (nullptr != (CharSetStr = ExtractFromContentType(ContentType, "charset="))) { + head->CP = GetCharsetFromString(CharSetStr, mir_strlen(CharSetStr)); + delete[] CharSetStr; + } + delete[] ContentType; + } + else if (0 == _strnicmp(items->name, "Importance", 10)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + if (head->Priority != -1) { + if (0 == strncmp(items->value, "low", 3)) + head->Priority = 5; + else if (0 == strncmp(items->value, "normal", 6)) + head->Priority = 3; + else if (0 == strncmp(items->value, "high", 4)) + head->Priority = 1; + } + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + else if (0 == _strnicmp(items->name, "X-Priority", 10)) { + if (items->value == nullptr) + continue; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, ""); + #endif + if ((*items->value >= '1') && (*items->value <= '5')) + head->Priority = *items->value - '0'; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + } + + } +} + +void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head) +{ + struct CShortHeader ShortHeader; + + memset(&ShortHeader, 0, sizeof(struct CShortHeader)); + ShortHeader.Priority = ShortHeader.CP = -1; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + ExtractShortHeader(items, &ShortHeader); + + head->Priority = ShortHeader.Priority == -1 ? 3 : ShortHeader.Priority; + CP = ShortHeader.CP == -1 ? CP : ShortHeader.CP; + #ifdef DEBUG_DECODE + if (NULL != ShortHeader.From) + DebugLog(DecodeFile, "%s%s%s%s%s%s\n"); + DebugLog(DecodeFile, "\n"); + #endif + + ConvertCodedStringToUnicode(ShortHeader.From, &head->From, CP, MIME_PLAIN); + + #ifdef DEBUG_DECODE + if (NULL != head->From) + DebugLogW(DecodeFile, L"%s\n", head->From); + #endif + ConvertCodedStringToUnicode(ShortHeader.FromNick, &head->FromNick, CP, MIME_MAIL); + #ifdef DEBUG_DECODE + if (NULL != head->FromNick) + DebugLogW(DecodeFile, L"%s\n", head->FromNick); + #endif + ConvertCodedStringToUnicode(ShortHeader.ReturnPath, &head->ReturnPath, CP, MIME_PLAIN); + #ifdef DEBUG_DECODE + if (NULL != head->ReturnPath) + DebugLogW(DecodeFile, L"%s\n", head->ReturnPath); + #endif + ConvertCodedStringToUnicode(ShortHeader.ReturnPathNick, &head->ReturnPathNick, CP, MIME_MAIL); + #ifdef DEBUG_DECODE + if (NULL != head->ReturnPathNick) + DebugLogW(DecodeFile, L"%s\n", head->ReturnPathNick); + #endif + ConvertCodedStringToUnicode(ShortHeader.Subject, &head->Subject, CP, MIME_PLAIN); + #ifdef DEBUG_DECODE + if (NULL != head->Subject) + DebugLogW(DecodeFile, L"%s\n", head->Subject); + #endif + ConvertCodedStringToUnicode(ShortHeader.Date, &head->Date, CP, MIME_PLAIN); + #ifdef DEBUG_DECODE + if (NULL != head->Date) + DebugLogW(DecodeFile, L"%s\n", head->Date); + #endif + + ConvertCodedStringToUnicode(ShortHeader.Body, &head->Body, CP, MIME_PLAIN); + #ifdef DEBUG_DECODE + if (NULL != head->Body) + DebugLogW(DecodeFile, L"%s\n", head->Body); + #endif + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + + DeleteShortHeaderContent(&ShortHeader); + + // head->From=L"Frommmm"; + // head->Subject=L"Subject"; + return; +} + +void DeleteShortHeaderContent(struct CShortHeader *head) +{ + if (head->From != nullptr) delete[] head->From; + if (head->FromNick != nullptr) delete[] head->FromNick; + if (head->ReturnPath != nullptr) delete[] head->ReturnPath; + if (head->ReturnPathNick != nullptr) delete[] head->ReturnPathNick; + if (head->Subject != nullptr) delete[] head->Subject; + if (head->Date != nullptr) delete[] head->Date; + if (head->To != nullptr) DeleteShortNames(head->To); + if (head->Cc != nullptr) DeleteShortNames(head->Cc); + if (head->Bcc != nullptr) DeleteShortNames(head->Bcc); + if (head->Body != nullptr) delete[] head->Body; +} + +void DeleteHeaderContent(struct CHeader *head) +{ + if (head->From != nullptr) delete[] head->From; + if (head->FromNick != nullptr) delete[] head->FromNick; + if (head->ReturnPath != nullptr) delete[] head->ReturnPath; + if (head->ReturnPathNick != nullptr) delete[] head->ReturnPathNick; + if (head->Subject != nullptr) delete[] head->Subject; + if (head->Date != nullptr) delete[] head->Date; + if (head->Body != nullptr) delete[] head->Body; + if (head->To != nullptr) DeleteNames(head->To); + if (head->Cc != nullptr) DeleteNames(head->Cc); + if (head->Bcc != nullptr) DeleteNames(head->Bcc); +} + +void DeleteNames(CMimeNames *Names) +{ + CMimeNames *Parser = Names; + for (; Parser != nullptr; Parser = Parser->Next) { + if (Parser->Value != nullptr) + delete[] Parser->Value; + if (Parser->ValueNick != nullptr) + delete[] Parser->ValueNick; + + CMimeNames *Old = Parser; + Parser = Parser->Next; + delete Old; + } +} + +void DeleteShortNames(CShortNames *Names) +{ + CShortNames *Parser = Names; + for (; Parser != nullptr; Parser = Parser->Next) { + if (Parser->Value != nullptr) + delete[] Parser->Value; + if (Parser->ValueNick != nullptr) + delete[] Parser->ValueNick; + + CShortNames *Old = Parser; + Parser = Parser->Next; + delete Old; + } +} + + +void inline ToLower(char *string) +{ + for (; *string != 0; string++) + if (*string >= 'A' && *string <= 'Z') *string = *string - 'A' + 'a'; +} + +#define TE_UNKNOWN +#define TE_QUOTEDPRINTABLE 1 +#define TE_BASE64 2 +struct APartDataType +{ + char *Src;//Input + char *ContType; + int CodePage; + char *TransEnc; + uint8_t TransEncType; //TE_something + char *body; + int bodyLen; + wchar_t *wBody; +}; + + +void ParseAPart(APartDataType *data) +{ + size_t len = mir_strlen(data->Src); + try { + char *finder = data->Src; + char *prev1, *prev2, *prev3; + + while (finder <= (data->Src + len)) { + while (ENDLINEWS(finder)) finder++; + + //at the start of line + if (finder > data->Src) { + if (*(finder - 2) == '\r' || *(finder - 2) == '\n') + *(finder - 2) = 0; + if (*(finder - 1) == '\r' || *(finder - 1) == '\n') + *(finder - 1) = 0; + } + prev1 = finder; + + while (*finder != ':' && !EOS(finder) && !ENDLINE(finder)) finder++; + if (ENDLINE(finder) || EOS(finder)) { + // no ":" in the line? here the body begins; + data->body = prev1; + break; + } + prev2 = finder++; + + while (WS(finder) && !EOS(finder)) finder++; + if (!EOS(finder)) + prev3 = finder; + else + break; + + do { + if (ENDLINEWS(finder)) finder += 2; //after endline information continues + while (!ENDLINE(finder) && !EOS(finder)) finder++; + } while (ENDLINEWS(finder)); + + if (!_strnicmp(prev1, "Content-type", prev2 - prev1)) { + data->ContType = prev3; + } + else if (!_strnicmp(prev1, "Content-Transfer-Encoding", prev2 - prev1)) { + data->TransEnc = prev3; + } + + if (EOS(finder)) + break; + finder++; + if (ENDLINE(finder)) { + finder++; + if (ENDLINE(finder)) { + // end of headers. message body begins + if (finder > data->Src) { + if (*(finder - 2) == '\r' || *(finder - 2) == '\n') + *(finder - 2) = 0; + if (*(finder - 1) == '\r' || *(finder - 1) == '\n') + *(finder - 1) = 0; + } + finder++; + if (ENDLINE(finder))finder++; + prev1 = finder; + while (!EOS(finder + 1))finder++; + if (ENDLINE(finder))finder--; + prev2 = finder; + if (prev2 > prev1) { // yes, we have body + data->body = prev1; + } + break; // there is nothing else + } + } + } + } + catch (...) { + MessageBox(nullptr, TranslateT("Translate header error"), L"", 0); + } + if (data->body) data->bodyLen = (int)mir_strlen(data->body); +} + +//from decode.cpp +int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); +int DecodeBase64(char *Src, char *Dst, int DstLen); +int ConvertStringToUnicode(char *stream, unsigned int cp, wchar_t **out); + +wchar_t *ParseMultipartBody(char *src, char *bond) +{ + char *srcback = _strdup(src); + size_t sizebond = mir_strlen(bond); + int numparts = 1; + int i; + char *courbond = srcback; + wchar_t *dest; + for (; (courbond = strstr(courbond, bond)); numparts++, courbond += sizebond); + APartDataType *partData = new APartDataType[numparts]; + memset(partData, 0, sizeof(APartDataType) * numparts); + partData[0].Src = courbond = srcback; + for (i = 1; (courbond = strstr(courbond, bond)); i++, courbond += sizebond) { + *(courbond - 2) = 0; + partData[i].Src = courbond + sizebond; + while (ENDLINE(partData[i].Src)) partData[i].Src++; + } + size_t resultSize = 0; + for (i = 0; i < numparts; i++) { + ParseAPart(&partData[i]); + if (partData[i].body) { + if (partData[i].TransEnc) { + if (!_stricmp(partData[i].TransEnc, "base64")) partData[i].TransEncType = TE_BASE64; + else if (!_stricmp(partData[i].TransEnc, "quoted-printable"))partData[i].TransEncType = TE_QUOTEDPRINTABLE; + } + if (partData[i].ContType) { + char *CharSetStr; + if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "charset="))) { + partData[i].CodePage = GetCharsetFromString(CharSetStr, mir_strlen(CharSetStr)); + delete[] CharSetStr; + } + } + if (partData[i].ContType && !_strnicmp(partData[i].ContType, "text", 4)) { + char *localBody = nullptr; + switch (partData[i].TransEncType) { + case TE_BASE64: + { + int size = partData[i].bodyLen * 3 / 4 + 5; + localBody = new char[size + 1]; + DecodeBase64(partData[i].body, localBody, size); + }break; + case TE_QUOTEDPRINTABLE: + { + int size = partData[i].bodyLen + 2; + localBody = new char[size + 1]; + DecodeQuotedPrintable(partData[i].body, localBody, size, FALSE); + }break; + } + ConvertStringToUnicode(localBody ? localBody : partData[i].body, partData[i].CodePage, &partData[i].wBody); + if (localBody) delete[] localBody; + } + else if (partData[i].ContType && !_strnicmp(partData[i].ContType, "multipart/", 10)) { + //Multipart in mulitipart recursive? should be SPAM. Ah well + char *bondary = nullptr; + if (nullptr != (bondary = ExtractFromContentType(partData[i].ContType, "boundary="))) { + partData[i].wBody = ParseMultipartBody(partData[i].body, bondary); + delete[] bondary; + } + else goto FailBackRaw; //multipart with no boundary? badly formatted messages. + } + else { +FailBackRaw: + ConvertStringToUnicode(partData[i].body, partData[i].CodePage, &partData[i].wBody); + } + resultSize += mir_wstrlen(partData[i].wBody); + }// if (partData[i].body) + resultSize += 100 + 4 + 3; //cr+nl+100+ 3*bullet + } + dest = new wchar_t[resultSize + 1]; + size_t destpos = 0; + for (i = 0; i < numparts; i++) { + if (i) { // part before first boudary should not have headers + char infoline[1024]; size_t linesize = 0; + mir_snprintf(infoline, "%s %d", Translate("Part"), i); + linesize = mir_strlen(infoline); + if (partData[i].TransEnc) { + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].TransEnc); + linesize = mir_strlen(infoline); + } + if (partData[i].ContType) { + char *CharSetStr = strchr(partData[i].ContType, ';'); + if (CharSetStr) { + CharSetStr[0] = 0; + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].ContType); + linesize = mir_strlen(infoline); + partData[i].ContType = CharSetStr + 1; + if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "charset="))) { + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", CharSetStr); + linesize = mir_strlen(infoline); + delete[] CharSetStr; + } + if (nullptr != (CharSetStr = ExtractFromContentType(partData[i].ContType, "name="))) { + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; \"%s\"", CharSetStr); + linesize = mir_strlen(infoline); + delete[] CharSetStr; + } + } + else { + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, "; %s", partData[i].ContType); + linesize = mir_strlen(infoline); + } + } + mir_snprintf(infoline + linesize, _countof(infoline) - linesize, ".\r\n"); + { + wchar_t *temp = nullptr; + dest[destpos] = dest[destpos + 1] = dest[destpos + 2] = 0x2022; // bullet; + destpos += 3; + ConvertStringToUnicode(infoline, CP_ACP, &temp); + size_t wsize = mir_wstrlen(temp); + mir_wstrcpy(&dest[destpos], temp); + destpos += wsize; + delete[] temp; + } + } // if (i) + + if (partData[i].wBody) { + size_t wsize = mir_wstrlen(partData[i].wBody); + mir_wstrcpy(&dest[destpos], partData[i].wBody); + destpos += wsize; + delete[] partData[i].wBody; + } + } + + free(srcback); + delete[] partData; + dest[resultSize] = 0;//just in case + return dest; +} diff --git a/protocols/YAMN/src/main.cpp b/protocols/YAMN/src/main.cpp index 72b831f765..c4979af1fb 100644 --- a/protocols/YAMN/src/main.cpp +++ b/protocols/YAMN/src/main.cpp @@ -1,324 +1,324 @@ -/* - * YAMN plugin main file - * Miranda homepage: http://miranda-icq.sourceforge.net/ - * YAMN homepage: http://www.majvan.host.sk/Projekty/YAMN - * - * initializes all variables for further work - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - - //-------------------------------------------------------------------------------------------------- - -wchar_t ProfileName[MAX_PATH]; -wchar_t UserDirectory[MAX_PATH]; - -wchar_t szMirandaDir[MAX_PATH]; -wchar_t szProfileDir[MAX_PATH]; - -BOOL UninstallPlugins; - -HANDLE hAccountFolder; - -HINSTANCE *hDllPlugins; -static int iDllPlugins = 0; - -YAMN_VARIABLES YAMNVar; - -CMPlugin g_plugin; - -HANDLE hNewMailHook; -HANDLE NoWriterEV; -HANDLE hTTButton; - -UINT SecTimer; - -#define FIXED_TAB_SIZE 100 // default value for fixed width tabs - -static void GetProfileDirectory(wchar_t *szPath, int cbPath) -//This is copied from Miranda's sources. In 0.2.1.0 it is needed, in newer vesions of Miranda use MS_DB_GETPROFILEPATH service -{ - wchar_t tszOldPath[MAX_PATH]; - Profile_GetPathW(_countof(tszOldPath), tszOldPath); - mir_wstrcat(tszOldPath, L"\\*.book"); - - VARSW ptszNewPath(L"%miranda_userdata%"); - - SHFILEOPSTRUCT file_op = { - nullptr, - FO_MOVE, - tszOldPath, - ptszNewPath, - FOF_NOERRORUI | FOF_NOCONFIRMATION | FOF_SILENT, - false, - nullptr, - L""}; - SHFileOperation(&file_op); - - wcsncpy(szPath, ptszNewPath, cbPath); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST}; - -///////////////////////////////////////////////////////////////////////////////////////// - -PLUGININFOEX pluginInfoEx = { - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - // {B047A7E5-027A-4CFC-8B18-EDA8345D2790} - {0xb047a7e5, 0x27a, 0x4cfc, {0x8b, 0x18, 0xed, 0xa8, 0x34, 0x5d, 0x27, 0x90}} -}; - -CMPlugin::CMPlugin() : - PLUGIN(YAMN_DBMODULE, pluginInfoEx) -{ - RegisterProtocol(PROTOTYPE_VIRTUAL); - SetUniqueId("Id"); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// The callback function - -BOOL CALLBACK EnumSystemCodePagesProc(LPTSTR cpStr) -{ - // Convert code page string to number - UINT cp = _wtoi(cpStr); - if (!IsValidCodePage(cp)) - return TRUE; - - // Get Code Page name - CPINFOEX info; - if (GetCPInfoEx(cp, 0, &info)) { - for (int i = 1; i < CPLENALL; i++) if (CodePageNamesAll[i].CP == cp) { - CodePageNamesAll[i].isValid = TRUE; - CPLENSUPP++; - break; - } - } - return TRUE; -} - -int SystemModulesLoaded(WPARAM, LPARAM) -{ - //Insert "Check mail (YAMN)" item to Miranda's menu - CMenuItem mi(&g_plugin); - - SET_UID(mi, 0xa01ff3d9, 0x53cb, 0x4406, 0x85, 0xd9, 0xf1, 0x90, 0x3a, 0x94, 0xed, 0xf4); - mi.position = 0xb0000000; - mi.hIcolibItem = g_plugin.getIconHandle(IDI_CHECKMAIL); - mi.name.a = LPGEN("Check &mail (All Account)"); - mi.pszService = MS_YAMN_FORCECHECK; - Menu_AddMainMenuItem(&mi); - - SET_UID(mi, 0xfe22191f, 0x40c8, 0x479f, 0x93, 0x5d, 0xa5, 0x17, 0x1f, 0x57, 0x2f, 0xcb); - mi.name.a = LPGEN("Check &mail (This Account)"); - mi.pszService = MS_YAMN_CLISTCONTEXT; - Menu_AddContactMenuItem(&mi, YAMN_DBMODULE); - - SET_UID(mi, 0x147c7800, 0x12d0, 0x4209, 0xab, 0xcc, 0xfa, 0x64, 0xc6, 0xb0, 0xa6, 0xeb); - mi.hIcolibItem = g_plugin.getIconHandle(IDI_LAUNCHAPP); - mi.name.a = LPGEN("Launch application"); - mi.pszService = MS_YAMN_CLISTCONTEXTAPP; - Menu_AddContactMenuItem(&mi, YAMN_DBMODULE); - - if (hAccountFolder = FoldersRegisterCustomPathW(LPGEN("YAMN"), LPGEN("YAMN Account Folder"), UserDirectory)) - FoldersGetCustomPathW(hAccountFolder, UserDirectory, MAX_PATH, UserDirectory); - - RegisterPOP3Plugin(0, 0); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static IconItem iconList[] = -{ - {LPGEN("Check mail"), "YAMN_Check", IDI_CHECKMAIL}, - {LPGEN("Launch application"), "YAMN_Launch", IDI_LAUNCHAPP}, - {LPGEN("New Mail"), "YAMN_NewMail", IDI_NEWMAIL}, - {LPGEN("Connect Fail"), "YAMN_ConnectFail", IDI_BADCONNECT}, -}; - -void LoadIcons() -{ - g_plugin.registerIcon("YAMN", iconList); -} - -static void LoadPlugins() -{ - wchar_t szSearchPath[MAX_PATH]; - mir_snwprintf(szSearchPath, L"%s\\Plugins\\YAMN\\*.dll", szMirandaDir); - - hDllPlugins = nullptr; - - WIN32_FIND_DATA fd; - HANDLE hFind = FindFirstFile(szSearchPath, &fd); - if (hFind != INVALID_HANDLE_VALUE) { - do { - //rewritten from Miranda sources... Needed because Win32 API has a bug in FindFirstFile, search is done for *.dlllllll... too - wchar_t *dot = wcsrchr(fd.cFileName, '.'); - if (dot == nullptr) - continue; - - // we have a dot - int len = (int)mir_wstrlen(fd.cFileName); // find the length of the string - wchar_t *end = fd.cFileName + len; // get a pointer to the NULL - int safe = (end - dot) - 1; // figure out how many chars after the dot are "safe", not including NULL - - if ((safe != 3) || (mir_wstrcmpi(dot + 1, L"dll") != 0)) //not bound, however the "dll" string should mean only 3 chars are compared - continue; - - wchar_t szPluginPath[MAX_PATH]; - mir_snwprintf(szPluginPath, L"%s\\Plugins\\YAMN\\%s", szMirandaDir, fd.cFileName); - HINSTANCE hDll = LoadLibrary(szPluginPath); - if (hDll == nullptr) - continue; - - LOADFILTERFCN LoadFilter = (LOADFILTERFCN)GetProcAddress(hDll, "LoadFilter"); - if (nullptr == LoadFilter) { - FreeLibrary(hDll); - hDll = nullptr; - continue; - } - - if (!LoadFilter(GetFcnPtrSvc)) { - FreeLibrary(hDll); - hDll = nullptr; - } - - if (hDll != nullptr) { - hDllPlugins = (HINSTANCE *)realloc(hDllPlugins, (iDllPlugins + 1) * sizeof(HINSTANCE)); - hDllPlugins[iDllPlugins++] = hDll; - } - } while (FindNextFile(hFind, &fd)); - - FindClose(hFind); - } -} - -int CMPlugin::Load() -{ - // we get the Miranda Root Path - PathToAbsoluteW(L".", szMirandaDir); - - // retrieve the current profile name - Profile_GetNameW(_countof(ProfileName), ProfileName); - wchar_t *fc = wcsrchr(ProfileName, '.'); - if (fc != nullptr) *fc = 0; - - // we get the user path where our yamn-account.book.ini is stored from mirandaboot.ini file - GetProfileDirectory(UserDirectory, _countof(UserDirectory)); - - // Enumerate all the code pages available for the System Locale - EnumSystemCodePages(EnumSystemCodePagesProc, CP_INSTALLED); - CodePageNamesSupp = new _tcptable[CPLENSUPP]; - for (int i = 0, k = 0; i < CPLENALL; i++) - if (CodePageNamesAll[i].isValid) { - CodePageNamesSupp[k] = CodePageNamesAll[i]; - k++; - } - - if (nullptr == (NoWriterEV = CreateEvent(nullptr, TRUE, TRUE, nullptr))) - return 1; - if (nullptr == (WriteToFileEV = CreateEvent(nullptr, FALSE, FALSE, nullptr))) - return 1; - if (nullptr == (ExitEV = CreateEvent(nullptr, TRUE, FALSE, nullptr))) - return 1; - - PosX = g_plugin.getDword(YAMN_DBPOSX, 0); - PosY = g_plugin.getDword(YAMN_DBPOSY, 0); - SizeX = g_plugin.getDword(YAMN_DBSIZEX, 800); - SizeY = g_plugin.getDword(YAMN_DBSIZEY, 200); - - HeadPosX = g_plugin.getDword(YAMN_DBMSGPOSX, 0); - HeadPosY = g_plugin.getDword(YAMN_DBMSGPOSY, 0); - HeadSizeX = g_plugin.getDword(YAMN_DBMSGSIZEX, 690); - HeadSizeY = g_plugin.getDword(YAMN_DBMSGSIZEY, 300); - HeadSplitPos = g_plugin.getWord(YAMN_DBMSGPOSSPLIT, 250); - - optDateTime = g_plugin.getByte(YAMN_DBTIMEOPTIONS, optDateTime); - - // Create new window queues for broadcast messages - YAMNVar.MessageWnds = WindowList_Create(); - YAMNVar.NewMailAccountWnd = WindowList_Create(); - YAMNVar.Shutdown = FALSE; - - hCurSplitNS = LoadCursor(nullptr, IDC_SIZENS); - hCurSplitWE = LoadCursor(nullptr, IDC_SIZEWE); - - #ifdef _DEBUG - InitDebug(); - #endif - - CreateServiceFunctions(); - - g_plugin.addSound(YAMN_NEWMAILSOUND, L"YAMN", YAMN_NEWMAILSNDDESC); - g_plugin.addSound(YAMN_CONNECTFAILSOUND, L"YAMN", YAMN_CONNECTFAILSNDDESC); - - HookEvents(); - - LoadIcons(); - LoadPlugins(); - - HOTKEYDESC hkd = {}; - hkd.pszName = "YAMN_hotkey"; - hkd.pszService = MS_YAMN_FORCECHECK; - hkd.szSection.a = YAMN_DBMODULE; - hkd.szDescription.a = LPGEN("Check mail"); - hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, VK_F11); - g_plugin.addHotkey(&hkd); - - //Create thread that will be executed every second - if (!(SecTimer = SetTimer(nullptr, 0, 1000, TimerProc))) - return 1; - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static void UnloadPlugins() -{ - if (hDllPlugins == nullptr) - return; - - for (int i = iDllPlugins - 1; i >= 0; i--) { - if (FreeLibrary(hDllPlugins[i])) { - hDllPlugins[i] = nullptr; //for safety - iDllPlugins--; - } - } - free((void *)hDllPlugins); - hDllPlugins = nullptr; -} - -int CMPlugin::Unload() -{ - #ifdef _DEBUG - UnInitDebug(); - #endif - - WindowList_Destroy(YAMNVar.MessageWnds); - WindowList_Destroy(YAMNVar.NewMailAccountWnd); - - DestroyCursor(hCurSplitNS); - DestroyCursor(hCurSplitWE); - - CloseHandle(NoWriterEV); - CloseHandle(WriteToFileEV); - CloseHandle(ExitEV); - - UnloadPlugins(); - - delete[] CodePageNamesSupp; - return 0; -} +/* + * YAMN plugin main file + * Miranda homepage: http://miranda-icq.sourceforge.net/ + * YAMN homepage: http://www.majvan.host.sk/Projekty/YAMN + * + * initializes all variables for further work + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + + //-------------------------------------------------------------------------------------------------- + +wchar_t ProfileName[MAX_PATH]; +wchar_t UserDirectory[MAX_PATH]; + +wchar_t szMirandaDir[MAX_PATH]; +wchar_t szProfileDir[MAX_PATH]; + +BOOL UninstallPlugins; + +HANDLE hAccountFolder; + +HINSTANCE *hDllPlugins; +static int iDllPlugins = 0; + +YAMN_VARIABLES YAMNVar; + +CMPlugin g_plugin; + +HANDLE hNewMailHook; +HANDLE NoWriterEV; +HANDLE hTTButton; + +UINT SecTimer; + +#define FIXED_TAB_SIZE 100 // default value for fixed width tabs + +static void GetProfileDirectory(wchar_t *szPath, int cbPath) +//This is copied from Miranda's sources. In 0.2.1.0 it is needed, in newer vesions of Miranda use MS_DB_GETPROFILEPATH service +{ + wchar_t tszOldPath[MAX_PATH]; + Profile_GetPathW(_countof(tszOldPath), tszOldPath); + mir_wstrcat(tszOldPath, L"\\*.book"); + + VARSW ptszNewPath(L"%miranda_userdata%"); + + SHFILEOPSTRUCT file_op = { + nullptr, + FO_MOVE, + tszOldPath, + ptszNewPath, + FOF_NOERRORUI | FOF_NOCONFIRMATION | FOF_SILENT, + false, + nullptr, + L""}; + SHFileOperation(&file_op); + + wcsncpy(szPath, ptszNewPath, cbPath); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST}; + +///////////////////////////////////////////////////////////////////////////////////////// + +PLUGININFOEX pluginInfoEx = { + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {B047A7E5-027A-4CFC-8B18-EDA8345D2790} + {0xb047a7e5, 0x27a, 0x4cfc, {0x8b, 0x18, 0xed, 0xa8, 0x34, 0x5d, 0x27, 0x90}} +}; + +CMPlugin::CMPlugin() : + PLUGIN(YAMN_DBMODULE, pluginInfoEx) +{ + RegisterProtocol(PROTOTYPE_VIRTUAL); + SetUniqueId("Id"); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// The callback function + +BOOL CALLBACK EnumSystemCodePagesProc(LPTSTR cpStr) +{ + // Convert code page string to number + UINT cp = _wtoi(cpStr); + if (!IsValidCodePage(cp)) + return TRUE; + + // Get Code Page name + CPINFOEX info; + if (GetCPInfoEx(cp, 0, &info)) { + for (int i = 1; i < CPLENALL; i++) if (CodePageNamesAll[i].CP == cp) { + CodePageNamesAll[i].isValid = TRUE; + CPLENSUPP++; + break; + } + } + return TRUE; +} + +int SystemModulesLoaded(WPARAM, LPARAM) +{ + //Insert "Check mail (YAMN)" item to Miranda's menu + CMenuItem mi(&g_plugin); + + SET_UID(mi, 0xa01ff3d9, 0x53cb, 0x4406, 0x85, 0xd9, 0xf1, 0x90, 0x3a, 0x94, 0xed, 0xf4); + mi.position = 0xb0000000; + mi.hIcolibItem = g_plugin.getIconHandle(IDI_CHECKMAIL); + mi.name.a = LPGEN("Check &mail (All Account)"); + mi.pszService = MS_YAMN_FORCECHECK; + Menu_AddMainMenuItem(&mi); + + SET_UID(mi, 0xfe22191f, 0x40c8, 0x479f, 0x93, 0x5d, 0xa5, 0x17, 0x1f, 0x57, 0x2f, 0xcb); + mi.name.a = LPGEN("Check &mail (This Account)"); + mi.pszService = MS_YAMN_CLISTCONTEXT; + Menu_AddContactMenuItem(&mi, YAMN_DBMODULE); + + SET_UID(mi, 0x147c7800, 0x12d0, 0x4209, 0xab, 0xcc, 0xfa, 0x64, 0xc6, 0xb0, 0xa6, 0xeb); + mi.hIcolibItem = g_plugin.getIconHandle(IDI_LAUNCHAPP); + mi.name.a = LPGEN("Launch application"); + mi.pszService = MS_YAMN_CLISTCONTEXTAPP; + Menu_AddContactMenuItem(&mi, YAMN_DBMODULE); + + if (hAccountFolder = FoldersRegisterCustomPathW(LPGEN("YAMN"), LPGEN("YAMN Account Folder"), UserDirectory)) + FoldersGetCustomPathW(hAccountFolder, UserDirectory, MAX_PATH, UserDirectory); + + RegisterPOP3Plugin(0, 0); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static IconItem iconList[] = +{ + {LPGEN("Check mail"), "YAMN_Check", IDI_CHECKMAIL}, + {LPGEN("Launch application"), "YAMN_Launch", IDI_LAUNCHAPP}, + {LPGEN("New Mail"), "YAMN_NewMail", IDI_NEWMAIL}, + {LPGEN("Connect Fail"), "YAMN_ConnectFail", IDI_BADCONNECT}, +}; + +void LoadIcons() +{ + g_plugin.registerIcon("YAMN", iconList); +} + +static void LoadPlugins() +{ + wchar_t szSearchPath[MAX_PATH]; + mir_snwprintf(szSearchPath, L"%s\\Plugins\\YAMN\\*.dll", szMirandaDir); + + hDllPlugins = nullptr; + + WIN32_FIND_DATA fd; + HANDLE hFind = FindFirstFile(szSearchPath, &fd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + //rewritten from Miranda sources... Needed because Win32 API has a bug in FindFirstFile, search is done for *.dlllllll... too + wchar_t *dot = wcsrchr(fd.cFileName, '.'); + if (dot == nullptr) + continue; + + // we have a dot + int len = (int)mir_wstrlen(fd.cFileName); // find the length of the string + wchar_t *end = fd.cFileName + len; // get a pointer to the NULL + int safe = (end - dot) - 1; // figure out how many chars after the dot are "safe", not including NULL + + if ((safe != 3) || (mir_wstrcmpi(dot + 1, L"dll") != 0)) //not bound, however the "dll" string should mean only 3 chars are compared + continue; + + wchar_t szPluginPath[MAX_PATH]; + mir_snwprintf(szPluginPath, L"%s\\Plugins\\YAMN\\%s", szMirandaDir, fd.cFileName); + HINSTANCE hDll = LoadLibrary(szPluginPath); + if (hDll == nullptr) + continue; + + LOADFILTERFCN LoadFilter = (LOADFILTERFCN)GetProcAddress(hDll, "LoadFilter"); + if (nullptr == LoadFilter) { + FreeLibrary(hDll); + hDll = nullptr; + continue; + } + + if (!LoadFilter(GetFcnPtrSvc)) { + FreeLibrary(hDll); + hDll = nullptr; + } + + if (hDll != nullptr) { + hDllPlugins = (HINSTANCE *)realloc(hDllPlugins, (iDllPlugins + 1) * sizeof(HINSTANCE)); + hDllPlugins[iDllPlugins++] = hDll; + } + } while (FindNextFile(hFind, &fd)); + + FindClose(hFind); + } +} + +int CMPlugin::Load() +{ + // we get the Miranda Root Path + PathToAbsoluteW(L".", szMirandaDir); + + // retrieve the current profile name + Profile_GetNameW(_countof(ProfileName), ProfileName); + wchar_t *fc = wcsrchr(ProfileName, '.'); + if (fc != nullptr) *fc = 0; + + // we get the user path where our yamn-account.book.ini is stored from mirandaboot.ini file + GetProfileDirectory(UserDirectory, _countof(UserDirectory)); + + // Enumerate all the code pages available for the System Locale + EnumSystemCodePages(EnumSystemCodePagesProc, CP_INSTALLED); + CodePageNamesSupp = new _tcptable[CPLENSUPP]; + for (int i = 0, k = 0; i < CPLENALL; i++) + if (CodePageNamesAll[i].isValid) { + CodePageNamesSupp[k] = CodePageNamesAll[i]; + k++; + } + + if (nullptr == (NoWriterEV = CreateEvent(nullptr, TRUE, TRUE, nullptr))) + return 1; + if (nullptr == (WriteToFileEV = CreateEvent(nullptr, FALSE, FALSE, nullptr))) + return 1; + if (nullptr == (ExitEV = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + return 1; + + PosX = g_plugin.getDword(YAMN_DBPOSX, 0); + PosY = g_plugin.getDword(YAMN_DBPOSY, 0); + SizeX = g_plugin.getDword(YAMN_DBSIZEX, 800); + SizeY = g_plugin.getDword(YAMN_DBSIZEY, 200); + + HeadPosX = g_plugin.getDword(YAMN_DBMSGPOSX, 0); + HeadPosY = g_plugin.getDword(YAMN_DBMSGPOSY, 0); + HeadSizeX = g_plugin.getDword(YAMN_DBMSGSIZEX, 690); + HeadSizeY = g_plugin.getDword(YAMN_DBMSGSIZEY, 300); + HeadSplitPos = g_plugin.getWord(YAMN_DBMSGPOSSPLIT, 250); + + optDateTime = g_plugin.getByte(YAMN_DBTIMEOPTIONS, optDateTime); + + // Create new window queues for broadcast messages + YAMNVar.MessageWnds = WindowList_Create(); + YAMNVar.NewMailAccountWnd = WindowList_Create(); + YAMNVar.Shutdown = FALSE; + + hCurSplitNS = LoadCursor(nullptr, IDC_SIZENS); + hCurSplitWE = LoadCursor(nullptr, IDC_SIZEWE); + + #ifdef _DEBUG + InitDebug(); + #endif + + CreateServiceFunctions(); + + g_plugin.addSound(YAMN_NEWMAILSOUND, L"YAMN", YAMN_NEWMAILSNDDESC); + g_plugin.addSound(YAMN_CONNECTFAILSOUND, L"YAMN", YAMN_CONNECTFAILSNDDESC); + + HookEvents(); + + LoadIcons(); + LoadPlugins(); + + HOTKEYDESC hkd = {}; + hkd.pszName = "YAMN_hotkey"; + hkd.pszService = MS_YAMN_FORCECHECK; + hkd.szSection.a = YAMN_DBMODULE; + hkd.szDescription.a = LPGEN("Check mail"); + hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, VK_F11); + g_plugin.addHotkey(&hkd); + + //Create thread that will be executed every second + if (!(SecTimer = SetTimer(nullptr, 0, 1000, TimerProc))) + return 1; + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void UnloadPlugins() +{ + if (hDllPlugins == nullptr) + return; + + for (int i = iDllPlugins - 1; i >= 0; i--) { + if (FreeLibrary(hDllPlugins[i])) { + hDllPlugins[i] = nullptr; //for safety + iDllPlugins--; + } + } + free((void *)hDllPlugins); + hDllPlugins = nullptr; +} + +int CMPlugin::Unload() +{ + #ifdef _DEBUG + UnInitDebug(); + #endif + + WindowList_Destroy(YAMNVar.MessageWnds); + WindowList_Destroy(YAMNVar.NewMailAccountWnd); + + DestroyCursor(hCurSplitNS); + DestroyCursor(hCurSplitWE); + + CloseHandle(NoWriterEV); + CloseHandle(WriteToFileEV); + CloseHandle(ExitEV); + + UnloadPlugins(); + + delete[] CodePageNamesSupp; + return 0; +} diff --git a/protocols/YAMN/src/main.h b/protocols/YAMN/src/main.h index cdcf5c285c..971ba43c9f 100644 --- a/protocols/YAMN/src/main.h +++ b/protocols/YAMN/src/main.h @@ -1,37 +1,37 @@ -#ifndef __MAIN_H -#define __MAIN_H - -#define YAMN_NEWMAILSNDDESC LPGENW("New mail message") -#define YAMN_CONNECTFAILSNDDESC LPGENW("Connect failed") -#define YAMN_CONNECTFAILSOUND "YAMN/Sound/ConnectFail" -#define YAMN_NEWMAILSOUND "YAMN/Sound/NewMail" - -#define YAMN_DBMODULE "YAMN" -#define YAMN_DBPOSX "MailBrowserWinX" -#define YAMN_DBPOSY "MailBrowserWinY" -#define YAMN_DBSIZEX "MailBrowserWinW" -#define YAMN_DBSIZEY "MailBrowserWinH" -#define YAMN_DBMSGPOSX "MailMessageWinX" -#define YAMN_DBMSGPOSY "MailMessageWinY" -#define YAMN_DBMSGSIZEX "MailMessageWinW" -#define YAMN_DBMSGSIZEY "MailMessageWinH" -#define YAMN_DBMSGPOSSPLIT "MailMessageSplitY" -#define YAMN_TTBFCHECK "ForceCheckTTB" -#define YAMN_CLOSEDELETE "CloseOnDelete" -#define YAMN_DBTIMEOPTIONS "MailBrowserTimeOpts" - -#define YAMN_DEFAULTHK MAKEWORD(VK_F11,MOD_CONTROL) - -#define SHOWDATELONG 0x01 -#define SHOWDATENOTODAY 0x02 -#define SHOWDATENOSECONDS 0x04 - -extern unsigned char optDateTime; - -// Loading Icon and checking for icolib -void LoadIcons(); - -typedef INT_PTR (*LOADFILTERFCN)(MIRANDASERVICE GetYAMNFcn); - -#endif - +#ifndef __MAIN_H +#define __MAIN_H + +#define YAMN_NEWMAILSNDDESC LPGENW("New mail message") +#define YAMN_CONNECTFAILSNDDESC LPGENW("Connect failed") +#define YAMN_CONNECTFAILSOUND "YAMN/Sound/ConnectFail" +#define YAMN_NEWMAILSOUND "YAMN/Sound/NewMail" + +#define YAMN_DBMODULE "YAMN" +#define YAMN_DBPOSX "MailBrowserWinX" +#define YAMN_DBPOSY "MailBrowserWinY" +#define YAMN_DBSIZEX "MailBrowserWinW" +#define YAMN_DBSIZEY "MailBrowserWinH" +#define YAMN_DBMSGPOSX "MailMessageWinX" +#define YAMN_DBMSGPOSY "MailMessageWinY" +#define YAMN_DBMSGSIZEX "MailMessageWinW" +#define YAMN_DBMSGSIZEY "MailMessageWinH" +#define YAMN_DBMSGPOSSPLIT "MailMessageSplitY" +#define YAMN_TTBFCHECK "ForceCheckTTB" +#define YAMN_CLOSEDELETE "CloseOnDelete" +#define YAMN_DBTIMEOPTIONS "MailBrowserTimeOpts" + +#define YAMN_DEFAULTHK MAKEWORD(VK_F11,MOD_CONTROL) + +#define SHOWDATELONG 0x01 +#define SHOWDATENOTODAY 0x02 +#define SHOWDATENOSECONDS 0x04 + +extern unsigned char optDateTime; + +// Loading Icon and checking for icolib +void LoadIcons(); + +typedef INT_PTR (*LOADFILTERFCN)(MIRANDASERVICE GetYAMNFcn); + +#endif + diff --git a/protocols/YAMN/src/proto/netlib.cpp b/protocols/YAMN/src/proto/netlib.cpp index cf477e8219..726860e459 100644 --- a/protocols/YAMN/src/proto/netlib.cpp +++ b/protocols/YAMN/src/proto/netlib.cpp @@ -1,232 +1,232 @@ -/* - * This code implements communication based on Miranda netlib library - * - * (c) majvan 2002-2004 - */ - -#include "../stdafx.h" - - //-------------------------------------------------------------------------------------------------- - -BOOL SSLLoaded = FALSE; -HNETLIBUSER hNetlibUser = nullptr; - -void __stdcall SSL_DebugLog(const char *fmt, ...) -{ - char str[4096]; - va_list vararg; - - va_start(vararg, fmt); - int tBytes = mir_vsnprintf(str, _countof(str), fmt, vararg); - if (tBytes == 0) - return; - - if (tBytes > 0) - str[tBytes] = 0; - else - str[sizeof(str) - 1] = 0; - - Netlib_Log(hNetlibUser, str); - va_end(vararg); -} - -HANDLE RegisterNLClient(char *name) -{ - #ifdef DEBUG_COMM - DebugLog(CommFile, ""); - #endif - - NETLIBUSER nlu = {}; - nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS; - nlu.szDescriptiveName.a = name; - nlu.szSettingsModule = (char *)name; - hNetlibUser = Netlib_RegisterUser(&nlu); - - #ifdef DEBUG_COMM - if (NULL == hNetlibUser) - DebugLog(CommFile, "\n"); - else - DebugLog(CommFile, "\n"); - #endif - return hNetlibUser; -} - -//Move connection to SSL -void CNLClient::SSLify() throw(DWORD) -{ - #ifdef DEBUG_COMM - SSL_DebugLog("Staring SSL..."); - #endif - int socket = Netlib_GetSocket(hConnection); - if (socket != INVALID_SOCKET) { - #ifdef DEBUG_COMM - SSL_DebugLog("Staring netlib core SSL"); - #endif - if (Netlib_StartSsl(hConnection, nullptr)) { - #ifdef DEBUG_COMM - SSL_DebugLog("Netlib core SSL started"); - #endif - isTLSed = true; - SSLLoaded = TRUE; - return; - } - } - - //ssl could not be created - throw NetworkError = (uint32_t)ESSL_CREATESSL; -} - -//Connects to the server through the sock -//if not success, exception is throwed -void CNLClient::Connect(const char *servername, const int port) throw(DWORD) -{ - NetworkError = SystemError = 0; - isTLSed = false; - - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - try { - if (nullptr == (hConnection = Netlib_OpenConnection(hNetlibUser, servername, port))) { - SystemError = WSAGetLastError(); - throw NetworkError = (uint32_t)ENL_CONNECT; - } - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - return; - } - catch (...) { - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - throw; - } -} - -//Performs a simple query -// query- command to send -int CNLClient::LocalNetlib_Send(HNETLIBCONN hConn, const char *buf, int len, int flags) -{ - return Netlib_Send(hConn, buf, len, flags); -} - -void CNLClient::Send(const char *query) throw(DWORD) -{ - unsigned int Sent; - - if (nullptr == query) - return; - if (hConnection == nullptr) - return; - #ifdef DEBUG_COMM - DebugLog(CommFile, "%s", query); - #endif - try { - if ((SOCKET_ERROR == (Sent = LocalNetlib_Send(hConnection, query, (int)mir_strlen(query), MSG_DUMPASTEXT))) || Sent != (unsigned int)mir_strlen(query)) { - SystemError = WSAGetLastError(); - throw NetworkError = (uint32_t)ENL_SEND; - } - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - } - catch (...) { - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - throw; - } -} - -//Reads data from socket -// buf- buffer where to store max. buflen of received characters -// if buf is NULL, creates buffer of buflen size -// buf is NULL by default -//You need free() returned buffer, which can be allocated in this function -//if not success, exception is throwed - -int CNLClient::LocalNetlib_Recv(HNETLIBCONN hConn, char *buf, int len, int flags) -{ - int iReturn = Netlib_Recv(hConn, buf, len, flags); - if (isTLSed) { - #ifdef DEBUG_COMM - SSL_DebugLog("SSL recv: %s", buf); - #endif - } - - return iReturn; -} - -char *CNLClient::Recv(char *buf, int buflen) throw(DWORD) -{ - #ifdef DEBUG_COMM - DebugLog(CommFile, ""); - #endif - try { - if (buf == nullptr) - buf = (char *)malloc(sizeof(char) * (buflen + 1)); - if (buf == nullptr) - throw NetworkError = (uint32_t)ENL_RECVALLOC; - - if (!isTLSed) { - NETLIBSELECT nls = {}; - nls.dwTimeout = 60000; - nls.hReadConns[0] = hConnection; - switch (Netlib_Select(&nls)) { - case SOCKET_ERROR: - free(buf); - SystemError = WSAGetLastError(); - throw NetworkError = (uint32_t)ENL_RECV; - case 0: // time out! - free(buf); - throw NetworkError = (uint32_t)ENL_TIMEOUT; - } - } - - memset(buf, 0, buflen); - if (SOCKET_ERROR == (Rcv = LocalNetlib_Recv(hConnection, buf, buflen, MSG_DUMPASTEXT))) { - free(buf); - SystemError = WSAGetLastError(); - throw NetworkError = (uint32_t)ENL_RECV; - } - if (!Rcv) { - free(buf); - SystemError = WSAGetLastError(); - throw NetworkError = (uint32_t)ENL_RECV; - } - #ifdef DEBUG_COMM - *(buf + Rcv) = 0; //end the buffer to write it to file - DebugLog(CommFile, "%s", buf); - DebugLog(CommFile, "\n"); - #endif - return(buf); - } - catch (...) { - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - throw; - } -} - -//Closes netlib connection -void CNLClient::Disconnect() -{ - Netlib_CloseHandle(hConnection); - hConnection = nullptr; -} - -//Uninitializes netlib library -void UnregisterNLClient() -{ - #ifdef DEBUG_COMM - DebugLog(CommFile, ""); - #endif - - Netlib_CloseHandle(hNetlibUser); - hNetlibUser = nullptr; - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif -} +/* + * This code implements communication based on Miranda netlib library + * + * (c) majvan 2002-2004 + */ + +#include "../stdafx.h" + + //-------------------------------------------------------------------------------------------------- + +BOOL SSLLoaded = FALSE; +HNETLIBUSER hNetlibUser = nullptr; + +void __stdcall SSL_DebugLog(const char *fmt, ...) +{ + char str[4096]; + va_list vararg; + + va_start(vararg, fmt); + int tBytes = mir_vsnprintf(str, _countof(str), fmt, vararg); + if (tBytes == 0) + return; + + if (tBytes > 0) + str[tBytes] = 0; + else + str[sizeof(str) - 1] = 0; + + Netlib_Log(hNetlibUser, str); + va_end(vararg); +} + +HANDLE RegisterNLClient(char *name) +{ + #ifdef DEBUG_COMM + DebugLog(CommFile, ""); + #endif + + NETLIBUSER nlu = {}; + nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS; + nlu.szDescriptiveName.a = name; + nlu.szSettingsModule = (char *)name; + hNetlibUser = Netlib_RegisterUser(&nlu); + + #ifdef DEBUG_COMM + if (NULL == hNetlibUser) + DebugLog(CommFile, "\n"); + else + DebugLog(CommFile, "\n"); + #endif + return hNetlibUser; +} + +//Move connection to SSL +void CNLClient::SSLify() throw(DWORD) +{ + #ifdef DEBUG_COMM + SSL_DebugLog("Staring SSL..."); + #endif + int socket = Netlib_GetSocket(hConnection); + if (socket != INVALID_SOCKET) { + #ifdef DEBUG_COMM + SSL_DebugLog("Staring netlib core SSL"); + #endif + if (Netlib_StartSsl(hConnection, nullptr)) { + #ifdef DEBUG_COMM + SSL_DebugLog("Netlib core SSL started"); + #endif + isTLSed = true; + SSLLoaded = TRUE; + return; + } + } + + //ssl could not be created + throw NetworkError = (uint32_t)ESSL_CREATESSL; +} + +//Connects to the server through the sock +//if not success, exception is throwed +void CNLClient::Connect(const char *servername, const int port) throw(DWORD) +{ + NetworkError = SystemError = 0; + isTLSed = false; + + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + try { + if (nullptr == (hConnection = Netlib_OpenConnection(hNetlibUser, servername, port))) { + SystemError = WSAGetLastError(); + throw NetworkError = (uint32_t)ENL_CONNECT; + } + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + return; + } + catch (...) { + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + throw; + } +} + +//Performs a simple query +// query- command to send +int CNLClient::LocalNetlib_Send(HNETLIBCONN hConn, const char *buf, int len, int flags) +{ + return Netlib_Send(hConn, buf, len, flags); +} + +void CNLClient::Send(const char *query) throw(DWORD) +{ + unsigned int Sent; + + if (nullptr == query) + return; + if (hConnection == nullptr) + return; + #ifdef DEBUG_COMM + DebugLog(CommFile, "%s", query); + #endif + try { + if ((SOCKET_ERROR == (Sent = LocalNetlib_Send(hConnection, query, (int)mir_strlen(query), MSG_DUMPASTEXT))) || Sent != (unsigned int)mir_strlen(query)) { + SystemError = WSAGetLastError(); + throw NetworkError = (uint32_t)ENL_SEND; + } + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + } + catch (...) { + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + throw; + } +} + +//Reads data from socket +// buf- buffer where to store max. buflen of received characters +// if buf is NULL, creates buffer of buflen size +// buf is NULL by default +//You need free() returned buffer, which can be allocated in this function +//if not success, exception is throwed + +int CNLClient::LocalNetlib_Recv(HNETLIBCONN hConn, char *buf, int len, int flags) +{ + int iReturn = Netlib_Recv(hConn, buf, len, flags); + if (isTLSed) { + #ifdef DEBUG_COMM + SSL_DebugLog("SSL recv: %s", buf); + #endif + } + + return iReturn; +} + +char *CNLClient::Recv(char *buf, int buflen) throw(DWORD) +{ + #ifdef DEBUG_COMM + DebugLog(CommFile, ""); + #endif + try { + if (buf == nullptr) + buf = (char *)malloc(sizeof(char) * (buflen + 1)); + if (buf == nullptr) + throw NetworkError = (uint32_t)ENL_RECVALLOC; + + if (!isTLSed) { + NETLIBSELECT nls = {}; + nls.dwTimeout = 60000; + nls.hReadConns[0] = hConnection; + switch (Netlib_Select(&nls)) { + case SOCKET_ERROR: + free(buf); + SystemError = WSAGetLastError(); + throw NetworkError = (uint32_t)ENL_RECV; + case 0: // time out! + free(buf); + throw NetworkError = (uint32_t)ENL_TIMEOUT; + } + } + + memset(buf, 0, buflen); + if (SOCKET_ERROR == (Rcv = LocalNetlib_Recv(hConnection, buf, buflen, MSG_DUMPASTEXT))) { + free(buf); + SystemError = WSAGetLastError(); + throw NetworkError = (uint32_t)ENL_RECV; + } + if (!Rcv) { + free(buf); + SystemError = WSAGetLastError(); + throw NetworkError = (uint32_t)ENL_RECV; + } + #ifdef DEBUG_COMM + *(buf + Rcv) = 0; //end the buffer to write it to file + DebugLog(CommFile, "%s", buf); + DebugLog(CommFile, "\n"); + #endif + return(buf); + } + catch (...) { + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + throw; + } +} + +//Closes netlib connection +void CNLClient::Disconnect() +{ + Netlib_CloseHandle(hConnection); + hConnection = nullptr; +} + +//Uninitializes netlib library +void UnregisterNLClient() +{ + #ifdef DEBUG_COMM + DebugLog(CommFile, ""); + #endif + + Netlib_CloseHandle(hNetlibUser); + hNetlibUser = nullptr; + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif +} diff --git a/protocols/YAMN/src/proto/pop3/pop3.cpp b/protocols/YAMN/src/proto/pop3/pop3.cpp index d8f704dbd6..8d24a5218c 100644 --- a/protocols/YAMN/src/proto/pop3/pop3.cpp +++ b/protocols/YAMN/src/proto/pop3/pop3.cpp @@ -1,344 +1,344 @@ -/* - * This code implements basics of POP3 protocol - * - * (c) majvan 2002-2004 - */ -/* This was made from the libspopc project - * copyright c 2002 Benoit Rouits - * released under the terms of GNU LGPL - * (GNU Lesser General Public Licence). - * libspopc offers simple API for a pop3 client (MTA). - * See RFC 1725 for pop3 specifications. - * more information on http://brouits.free.fr/libspopc/ - */ -/* - * This file is not original and is changed by majvan - * for mail checker purpose. Please see original web page to - * obtain the original. I rewrote it in C++, but good ideas were, - * I think, unchanged. - * - * Note that this file was not designed to work under Unix. It's - * needed to add Unix-specific features. I was interested only in - * Windows for my project. majvan - * - */ - -#include "../../stdafx.h" - - //-------------------------------------------------------------------------------------------------- - - //Connects to the server through the netlib - //if not success, exception is throwed - //returns welcome string returned by server - //sets AckFlag -char *CPop3Client::Connect(const char *servername, const int port, BOOL UseSSL, BOOL NoTLS) -{ - if (Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - delete NetClient; - SSL = UseSSL; - NetClient = new CNLClient; - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "Connect:servername: %s port:%d\n", servername, port); - #endif - POP3Error = EPOP3_CONNECT; - NetClient->Connect(servername, port); - POP3Error = 0; - - if (SSL) { - try { - NetClient->SSLify(); - } - catch (...) { - NetClient->Disconnect(); - return nullptr; - } - } - - char *temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK); - extern BOOL SSLLoaded; - if (!NoTLS & !(SSL)) { - if (NetClient->Stopped) //check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - NetClient->Send("STLS\r\n"); - free(temp); - temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK); - if (AckFlag == POP3_FOK) { // Ok, we are going to tls - try { - NetClient->SSLify(); - } - catch (...) { - NetClient->Disconnect(); - return nullptr; - } - } - } - return temp; -} - -//Receives data to the end of packet -// prev- previous data read (appends to this string next received data) -// mode- mode of packet. -// Packet can end with ack state (+OK or -ERR): set mode to POP3_SEARCHACK -// If packet ends with '.' (end of string), set mode to POP3_SEARCHDOT -// size- received data are stored to memory, but if length of data is more than allocated memory, function allocates -// new memory. New allocated memory has allocated size more bytes -// This value can be selectable: if you think it is better to reallocate by 1kB size, select size to 1024, -// default is 128. You do not need to use this parameter - -char *CPop3Client::RecvRest(char *prev, int mode, int size) -{ - int SizeRead = 0; - int SizeLeft = size - NetClient->Rcv; - int RcvAll = NetClient->Rcv; - char *LastString, *PrevString = prev; - - AckFlag = 0; - - while (((mode == POP3_SEARCHDOT) && !SearchFromEnd(PrevString + RcvAll - 1, RcvAll - 3, POP3_SEARCHDOT) && !SearchFromStart(PrevString, 2, POP3_SEARCHERR)) || //we are looking for dot or -err phrase - ((mode == POP3_SEARCHACK) && (!SearchFromStart(PrevString, RcvAll - 3, mode) || !((RcvAll > 3) && SearchFromEnd(PrevString + RcvAll - 1, 1, POP3_SEARCHNL))))) //we are looking for +ok or -err phrase ended with newline - { //if not found - if (NetClient->Stopped) //check if we can work with this POP3 client session - { - if (PrevString != nullptr) - free(PrevString); - throw POP3Error = (uint32_t)EPOP3_STOPPED; - } - if (SizeLeft == 0) //if block is full - { - SizeRead += size; - SizeLeft = size; - LastString = NetClient->Recv(nullptr, SizeLeft); - PrevString = (char *)realloc(PrevString, sizeof(char) * (SizeRead + size)); - if (PrevString == nullptr) - throw POP3Error = (uint32_t)EPOP3_RESTALLOC; - memcpy(PrevString + SizeRead, LastString, size); - free(LastString); - } - else - NetClient->Recv(PrevString + RcvAll, SizeLeft); //to Rcv stores received bytes - SizeLeft = SizeLeft - NetClient->Rcv; - RcvAll += NetClient->Rcv; - } - NetClient->Rcv = RcvAll; //at the end, store the number of all bytes, no the number of last received bytes - return PrevString; -} - -// CPop3Client::SearchFromEnd -// returns 1 if substring DOTLINE or ENDLINE found from end in bs bytes -// if you need to add condition for mode, insert it into switch statement -BOOL CPop3Client::SearchFromEnd(char *end, int bs, int mode) -{ - while (bs >= 0) { - switch (mode) { - case POP3_SEARCHDOT: - if (DOTLINE(end)) - return 1; - break; - - case POP3_SEARCHNL: - if (ENDLINE(end)) - return 1; - break; - } - end--; - bs--; - } - return 0; -} - -//Finds for a occurence of some pattern in string -// returns 1 if substring OKLINE, ERRLINE or any of them found from start in bs bytes -//call only this function to retrieve ack status (+OK or -ERR), because it sets flag AckFlag -//if you need to add condition for mode, insert it into switch statement -BOOL CPop3Client::SearchFromStart(char *start, int bs, int mode) -{ - while (bs >= 0) { - switch (mode) { - case POP3_SEARCHOK: - if (OKLINE(start)) { - AckFlag = POP3_FOK; - return 1; - } - break; - case POP3_SEARCHERR: - if (ERRLINE(start)) { - AckFlag = POP3_FERR; - return 1; - } - break; - case POP3_SEARCHACK: - if (ACKLINE(start)) { - OKLINE(start) ? AckFlag = POP3_FOK : AckFlag = POP3_FERR; - return 1; - } - break; - } - start++; - bs--; - } - return 0; -} - -//Performs "USER" pop query and returns server response -//sets AckFlag -char *CPop3Client::User(char *name) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - char *Result; - - mir_snprintf(query, "USER %s\r\n", name); - NetClient->Send(query); - Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); - if (AckFlag == POP3_FERR) - throw POP3Error = (uint32_t)EPOP3_BADUSER; - POP3Error = 0; - return Result; -} - -//Performs "PASS" pop query and returns server response -//sets AckFlag -char *CPop3Client::Pass(char *pw) -{ - if (NetClient->Stopped) //check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - mir_snprintf(query, "PASS %s\r\n", pw); - NetClient->Send(query); - - char *Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); - if (AckFlag == POP3_FERR) - throw POP3Error = (uint32_t)EPOP3_BADPASS; - return Result; -} - -//Performs "APOP" pop query and returns server response -//sets AckFlag -char *CPop3Client::APOP(char *name, char *pw, char *timestamp) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[512]; - char *Result; - unsigned char digest[16]; - - if (timestamp == nullptr) - throw POP3Error = (uint32_t)EPOP3_APOP; - mir_md5_state_s ctx; - mir_md5_init(&ctx); - mir_md5_append(&ctx, (const unsigned char *)timestamp, (unsigned int)mir_strlen(timestamp)); - mir_md5_append(&ctx, (const unsigned char *)pw, (unsigned int)mir_strlen(pw)); - mir_md5_finish(&ctx, digest); - - char hexdigest[40]; - mir_snprintf(query, "APOP %s %s\r\n", name, bin2hex(digest, sizeof(digest), hexdigest)); - - NetClient->Send(query); - Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); - if (AckFlag == POP3_FERR) - throw POP3Error = (uint32_t)EPOP3_BADUSER; - return Result; -} - -//Performs "QUIT" pop query and returns server response -//sets AckFlag -char *CPop3Client::Quit() -{ - char query[] = "QUIT\r\n"; - - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHACK); -} - -//Performs "STAT" pop query and returns server response -//sets AckFlag -char *CPop3Client::Stat() -{ - if (NetClient->Stopped) //check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[] = "STAT\r\n"; - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHACK); -} - -//Performs "LIST" pop query and returns server response -//sets AckFlag -char *CPop3Client::List() -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[] = "LIST\r\n"; - - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); -} - -//Performs "TOP" pop query and returns server response -//sets AckFlag -char *CPop3Client::Top(int nr, int lines) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - - mir_snprintf(query, "TOP %d %d\r\n", nr, lines); - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); -} - -//Performs "UIDL" pop query and returns server response -//sets AckFlag -char *CPop3Client::Uidl(int nr) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - if (nr) { - mir_snprintf(query, "UIDL %d\r\n", nr); - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHACK); - } - mir_snprintf(query, "UIDL\r\n"); - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); -} - -//Performs "DELE" pop query and returns server response -//sets AckFlag -char *CPop3Client::Dele(int nr) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - - mir_snprintf(query, "DELE %d\r\n", nr); - NetClient->Send(query); - return RecvRest(NetClient->Recv(), POP3_SEARCHACK); -} - -//Performs "RETR" pop query and returns server response -//sets AckFlag -char *CPop3Client::Retr(int nr) -{ - if (NetClient->Stopped) // check if we can work with this POP3 client session - throw POP3Error = (uint32_t)EPOP3_STOPPED; - - char query[128]; - mir_snprintf(query, "RETR %d\r\n", nr); - NetClient->Send(query); - - RecvRest(NetClient->Recv(), POP3_SEARCHACK); - return NetClient->Recv(); -} +/* + * This code implements basics of POP3 protocol + * + * (c) majvan 2002-2004 + */ +/* This was made from the libspopc project + * copyright c 2002 Benoit Rouits + * released under the terms of GNU LGPL + * (GNU Lesser General Public Licence). + * libspopc offers simple API for a pop3 client (MTA). + * See RFC 1725 for pop3 specifications. + * more information on http://brouits.free.fr/libspopc/ + */ +/* + * This file is not original and is changed by majvan + * for mail checker purpose. Please see original web page to + * obtain the original. I rewrote it in C++, but good ideas were, + * I think, unchanged. + * + * Note that this file was not designed to work under Unix. It's + * needed to add Unix-specific features. I was interested only in + * Windows for my project. majvan + * + */ + +#include "../../stdafx.h" + + //-------------------------------------------------------------------------------------------------- + + //Connects to the server through the netlib + //if not success, exception is throwed + //returns welcome string returned by server + //sets AckFlag +char *CPop3Client::Connect(const char *servername, const int port, BOOL UseSSL, BOOL NoTLS) +{ + if (Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + delete NetClient; + SSL = UseSSL; + NetClient = new CNLClient; + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "Connect:servername: %s port:%d\n", servername, port); + #endif + POP3Error = EPOP3_CONNECT; + NetClient->Connect(servername, port); + POP3Error = 0; + + if (SSL) { + try { + NetClient->SSLify(); + } + catch (...) { + NetClient->Disconnect(); + return nullptr; + } + } + + char *temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK); + extern BOOL SSLLoaded; + if (!NoTLS & !(SSL)) { + if (NetClient->Stopped) //check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + NetClient->Send("STLS\r\n"); + free(temp); + temp = RecvRest(NetClient->Recv(), POP3_SEARCHACK); + if (AckFlag == POP3_FOK) { // Ok, we are going to tls + try { + NetClient->SSLify(); + } + catch (...) { + NetClient->Disconnect(); + return nullptr; + } + } + } + return temp; +} + +//Receives data to the end of packet +// prev- previous data read (appends to this string next received data) +// mode- mode of packet. +// Packet can end with ack state (+OK or -ERR): set mode to POP3_SEARCHACK +// If packet ends with '.' (end of string), set mode to POP3_SEARCHDOT +// size- received data are stored to memory, but if length of data is more than allocated memory, function allocates +// new memory. New allocated memory has allocated size more bytes +// This value can be selectable: if you think it is better to reallocate by 1kB size, select size to 1024, +// default is 128. You do not need to use this parameter + +char *CPop3Client::RecvRest(char *prev, int mode, int size) +{ + int SizeRead = 0; + int SizeLeft = size - NetClient->Rcv; + int RcvAll = NetClient->Rcv; + char *LastString, *PrevString = prev; + + AckFlag = 0; + + while (((mode == POP3_SEARCHDOT) && !SearchFromEnd(PrevString + RcvAll - 1, RcvAll - 3, POP3_SEARCHDOT) && !SearchFromStart(PrevString, 2, POP3_SEARCHERR)) || //we are looking for dot or -err phrase + ((mode == POP3_SEARCHACK) && (!SearchFromStart(PrevString, RcvAll - 3, mode) || !((RcvAll > 3) && SearchFromEnd(PrevString + RcvAll - 1, 1, POP3_SEARCHNL))))) //we are looking for +ok or -err phrase ended with newline + { //if not found + if (NetClient->Stopped) //check if we can work with this POP3 client session + { + if (PrevString != nullptr) + free(PrevString); + throw POP3Error = (uint32_t)EPOP3_STOPPED; + } + if (SizeLeft == 0) //if block is full + { + SizeRead += size; + SizeLeft = size; + LastString = NetClient->Recv(nullptr, SizeLeft); + PrevString = (char *)realloc(PrevString, sizeof(char) * (SizeRead + size)); + if (PrevString == nullptr) + throw POP3Error = (uint32_t)EPOP3_RESTALLOC; + memcpy(PrevString + SizeRead, LastString, size); + free(LastString); + } + else + NetClient->Recv(PrevString + RcvAll, SizeLeft); //to Rcv stores received bytes + SizeLeft = SizeLeft - NetClient->Rcv; + RcvAll += NetClient->Rcv; + } + NetClient->Rcv = RcvAll; //at the end, store the number of all bytes, no the number of last received bytes + return PrevString; +} + +// CPop3Client::SearchFromEnd +// returns 1 if substring DOTLINE or ENDLINE found from end in bs bytes +// if you need to add condition for mode, insert it into switch statement +BOOL CPop3Client::SearchFromEnd(char *end, int bs, int mode) +{ + while (bs >= 0) { + switch (mode) { + case POP3_SEARCHDOT: + if (DOTLINE(end)) + return 1; + break; + + case POP3_SEARCHNL: + if (ENDLINE(end)) + return 1; + break; + } + end--; + bs--; + } + return 0; +} + +//Finds for a occurence of some pattern in string +// returns 1 if substring OKLINE, ERRLINE or any of them found from start in bs bytes +//call only this function to retrieve ack status (+OK or -ERR), because it sets flag AckFlag +//if you need to add condition for mode, insert it into switch statement +BOOL CPop3Client::SearchFromStart(char *start, int bs, int mode) +{ + while (bs >= 0) { + switch (mode) { + case POP3_SEARCHOK: + if (OKLINE(start)) { + AckFlag = POP3_FOK; + return 1; + } + break; + case POP3_SEARCHERR: + if (ERRLINE(start)) { + AckFlag = POP3_FERR; + return 1; + } + break; + case POP3_SEARCHACK: + if (ACKLINE(start)) { + OKLINE(start) ? AckFlag = POP3_FOK : AckFlag = POP3_FERR; + return 1; + } + break; + } + start++; + bs--; + } + return 0; +} + +//Performs "USER" pop query and returns server response +//sets AckFlag +char *CPop3Client::User(char *name) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + char *Result; + + mir_snprintf(query, "USER %s\r\n", name); + NetClient->Send(query); + Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); + if (AckFlag == POP3_FERR) + throw POP3Error = (uint32_t)EPOP3_BADUSER; + POP3Error = 0; + return Result; +} + +//Performs "PASS" pop query and returns server response +//sets AckFlag +char *CPop3Client::Pass(char *pw) +{ + if (NetClient->Stopped) //check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + mir_snprintf(query, "PASS %s\r\n", pw); + NetClient->Send(query); + + char *Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); + if (AckFlag == POP3_FERR) + throw POP3Error = (uint32_t)EPOP3_BADPASS; + return Result; +} + +//Performs "APOP" pop query and returns server response +//sets AckFlag +char *CPop3Client::APOP(char *name, char *pw, char *timestamp) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[512]; + char *Result; + unsigned char digest[16]; + + if (timestamp == nullptr) + throw POP3Error = (uint32_t)EPOP3_APOP; + mir_md5_state_s ctx; + mir_md5_init(&ctx); + mir_md5_append(&ctx, (const unsigned char *)timestamp, (unsigned int)mir_strlen(timestamp)); + mir_md5_append(&ctx, (const unsigned char *)pw, (unsigned int)mir_strlen(pw)); + mir_md5_finish(&ctx, digest); + + char hexdigest[40]; + mir_snprintf(query, "APOP %s %s\r\n", name, bin2hex(digest, sizeof(digest), hexdigest)); + + NetClient->Send(query); + Result = RecvRest(NetClient->Recv(), POP3_SEARCHACK); + if (AckFlag == POP3_FERR) + throw POP3Error = (uint32_t)EPOP3_BADUSER; + return Result; +} + +//Performs "QUIT" pop query and returns server response +//sets AckFlag +char *CPop3Client::Quit() +{ + char query[] = "QUIT\r\n"; + + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHACK); +} + +//Performs "STAT" pop query and returns server response +//sets AckFlag +char *CPop3Client::Stat() +{ + if (NetClient->Stopped) //check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[] = "STAT\r\n"; + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHACK); +} + +//Performs "LIST" pop query and returns server response +//sets AckFlag +char *CPop3Client::List() +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[] = "LIST\r\n"; + + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); +} + +//Performs "TOP" pop query and returns server response +//sets AckFlag +char *CPop3Client::Top(int nr, int lines) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + + mir_snprintf(query, "TOP %d %d\r\n", nr, lines); + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); +} + +//Performs "UIDL" pop query and returns server response +//sets AckFlag +char *CPop3Client::Uidl(int nr) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + if (nr) { + mir_snprintf(query, "UIDL %d\r\n", nr); + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHACK); + } + mir_snprintf(query, "UIDL\r\n"); + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHDOT); +} + +//Performs "DELE" pop query and returns server response +//sets AckFlag +char *CPop3Client::Dele(int nr) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + + mir_snprintf(query, "DELE %d\r\n", nr); + NetClient->Send(query); + return RecvRest(NetClient->Recv(), POP3_SEARCHACK); +} + +//Performs "RETR" pop query and returns server response +//sets AckFlag +char *CPop3Client::Retr(int nr) +{ + if (NetClient->Stopped) // check if we can work with this POP3 client session + throw POP3Error = (uint32_t)EPOP3_STOPPED; + + char query[128]; + mir_snprintf(query, "RETR %d\r\n", nr); + NetClient->Send(query); + + RecvRest(NetClient->Recv(), POP3_SEARCHACK); + return NetClient->Recv(); +} diff --git a/protocols/YAMN/src/proto/pop3/pop3comm.cpp b/protocols/YAMN/src/proto/pop3/pop3comm.cpp index 946b93ea54..fc0f5819bb 100644 --- a/protocols/YAMN/src/proto/pop3/pop3comm.cpp +++ b/protocols/YAMN/src/proto/pop3/pop3comm.cpp @@ -1,1329 +1,1329 @@ -/* - * This code implements POP3 server checking for new mail and so on. - * There's function SynchroPOP3 in this file- for checking and synchronising POP3 account - * and DeleteMailsPOP3- for deleting mails from POP3 server - * - * Note this file acts as main file for internal plugin. - * - * (c) majvan 2002-2004 - * 18/08 - */ - - -#include "../../stdafx.h" - -#define ERRORSTR_MAXLEN 1024 //in wide-chars - - //-------------------------------------------------------------------------------------------------- - -HANDLE hNetLib = nullptr; -PSCOUNTER CPOP3Account::AccountWriterSO = nullptr; - -//Creates new CPOP3Account structure -CAccount *WINAPI CreatePOP3Account(HYAMNPROTOPLUGIN Plugin, DWORD CAccountVersion); - -//Deletes CPOP3Account structure -void WINAPI DeletePOP3Account(CAccount *Which); - -//Sets stop flag to account -void WINAPI StopPOP3Account(CAccount *Which); - -//Function registers standard functions for YAMN -int RegisterPOP3Plugin(WPARAM, LPARAM); - -//Unloads all variables created on heap (delete[]) -DWORD WINAPI UnLoadPOP3(void *); - -//Function writes POP3 accounts using YAMN exported functions -DWORD WINAPI WritePOP3Accounts(); - -//Function stores plugin's data for account to file -DWORD WINAPI WritePOP3Options(HANDLE, CAccount *); - -//Function reads plugin's data for account from file -DWORD WINAPI ReadPOP3Options(CAccount *, char **, char *); - -//Creates new mail for an account -HYAMNMAIL WINAPI CreatePOP3Mail(CAccount *Account, DWORD CMimeMailVersion); - -//Function does all needed work when connection failed or any error occured -//Creates structure containing error code, closes internet session, runs "bad connect" function -static void PostErrorProc(HPOP3ACCOUNT ActualAccount, void *ParamToBadConnect, uint32_t POP3PluginParam, BOOL UseSSL); - -//Checks POP3 account and stores all info to account. It deletes old mails=> synchro -// WhichTemp- pointer to strucure containing needed information -DWORD WINAPI SynchroPOP3(struct CheckParam *WhichTemp); - -//Deletes mails from POP3 server -// WhichTemp- structure containing needed information (queued messages to delete) -//Function deletes from memory queue in WhichTemp structure -void __cdecl DeleteMailsPOP3(void *param); - -//Function makes readable message about error. It sends it back to YAMN, so YAMN then -//can show it to the message window -wchar_t *WINAPI GetErrorString(DWORD Code); - -//Function deletes string allocated in GetErrorString -void WINAPI DeleteErrorString(LPVOID String); - -//Extracts info from result of POP3's STAT command -// stream- source string -// len- length of source string -// mboxsize- adreess to integer, that receives size of mailbox -// mails- adreess to integer, that receives number of mails -void ExtractStat(char *stream, int *mboxsize, int *mails); - -//Extracts mail ID on mailbox -// stream- source string -// len- length of source string -// queue- address of first message, where first ID will be stored -void ExtractUIDL(char *stream, int len, HYAMNMAIL queue); - -//Extracts mail size on mailbox -// stream- source string -// len- length of source string -// queue- address of first message, where size of message #1 will be stored -void ExtractList(char *stream, int len, HYAMNMAIL queue); - -void ExtractMail(char *stream, int len, HYAMNMAIL queue); - -YAMNExportedFcns *pYAMNFcn = nullptr; -MailExportedFcns *pYAMNMailFcn = nullptr; - -YAMN_PROTOIMPORTFCN POP3ProtocolFunctions = -{ - CreatePOP3Account, - DeletePOP3Account, - StopPOP3Account, - WritePOP3Options, - ReadPOP3Options, - SynchroPOP3, - SynchroPOP3, - SynchroPOP3, - DeleteMailsPOP3, - GetErrorString, - nullptr, - DeleteErrorString, - WritePOP3Accounts, - nullptr, - UnLoadPOP3, -}; - -YAMN_MAILIMPORTFCN POP3MailFunctions = -{ - CreatePOP3Mail, - nullptr, - nullptr, - nullptr, -}; - -PYAMN_VARIABLES pYAMNVar = nullptr; -HYAMNPROTOPLUGIN POP3Plugin = nullptr; - -YAMN_PROTOREGISTRATION POP3ProtocolRegistration = -{ - "POP3 protocol (internal)", - __VERSION_STRING_DOTS, - __COPYRIGHT, - __DESCRIPTION, - __AUTHORWEB, -}; - -static wchar_t *FileName = nullptr; - -HANDLE RegisterNLClient(char *name); - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -CPOP3Account::CPOP3Account() -{ - //NOTE! This constructor constructs CAccount structure. If your plugin is not internal, - //you will need these constructors. All you need is in Account.cpp. Just copy to your source code - //constructor and destructor of CAccount. - UseInternetFree = CreateEvent(nullptr, FALSE, TRUE, nullptr); - InternetQueries = new SCOUNTER; - AbilityFlags = YAMN_ACC_BROWSE | YAMN_ACC_POPUP; - - SetAccountStatus((CAccount *)this, TranslateT("Disconnected")); -} - -CPOP3Account::~CPOP3Account() -{ - CloseHandle(UseInternetFree); - if (InternetQueries != nullptr) - delete InternetQueries; -} - -CAccount *WINAPI CreatePOP3Account(HYAMNPROTOPLUGIN, DWORD) -{ - //First, we should check whether CAccountVersion matches. - //But this is internal plugin, so YAMN's CAccount structure and our CAccount structure are - //the same, so we do not need to test version. Otherwise, if CAccount version does not match - //in your plugin, you should return NULL, like this: - // if (CAccountVersion != YAMN_ACCOUNTVERSION) return NULL; - - //Now it is needed to construct our POP3 account and return its handle - return (CAccount *)new struct CPOP3Account(); -} - -void WINAPI DeletePOP3Account(CAccount *Which) -{ - delete (HPOP3ACCOUNT)Which; -} - -void WINAPI StopPOP3Account(CAccount *Which) -{ - ((HPOP3ACCOUNT)Which)->Client.Stopped = TRUE; - if (((HPOP3ACCOUNT)Which)->Client.NetClient != nullptr) //we should inform also network client. Usefull only when network client implements this feature - ((HPOP3ACCOUNT)Which)->Client.NetClient->Stopped = TRUE; -} - -//This function is like main function for POP3 internal protocol -int RegisterPOP3Plugin(WPARAM, LPARAM) -{ - // Get YAMN variables we can use - if (nullptr == (pYAMNVar = (PYAMN_VARIABLES)CallService(MS_YAMN_GETVARIABLES, YAMN_VARIABLESVERSION, 0))) - return 0; - - // We have to get pointers to YAMN exported functions: allocate structure and fill it - if (nullptr == (pYAMNFcn = new struct YAMNExportedFcns)) { -LBL_Error: - UnLoadPOP3(nullptr); - return 0; - } - - // Register new pop3 user in netlib - if (nullptr == (hNetLib = RegisterNLClient("YAMN (POP3)"))) - goto LBL_Error; - - pYAMNFcn->SetProtocolPluginFcnImportFcn = (YAMN_SETPROTOCOLPLUGINFCNIMPORTFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SETPROTOCOLPLUGINFCNIMPORTID, 0); - pYAMNFcn->WaitToWriteFcn = (YAMN_WAITTOWRITEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WAITTOWRITEID, 0); - pYAMNFcn->WriteDoneFcn = (YAMN_WRITEDONEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WRITEDONEID, 0); - pYAMNFcn->WaitToReadFcn = (YAMN_WAITTOREADFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WAITTOREADID, 0); - pYAMNFcn->ReadDoneFcn = (YAMN_READDONEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_READDONEID, 0); - pYAMNFcn->SCGetNumberFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCGETNUMBERID, 0); - pYAMNFcn->SCIncFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCINCID, 0); - pYAMNFcn->SCDecFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCDECID, 0); - pYAMNFcn->SetStatusFcn = (YAMN_SETSTATUSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SETSTATUSID, 0); - pYAMNFcn->GetStatusFcn = (YAMN_GETSTATUSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_GETSTATUSID, 0); - - if (nullptr == (pYAMNMailFcn = new struct MailExportedFcns)) - goto LBL_Error; - - pYAMNMailFcn->SynchroMessagesFcn = (YAMN_SYNCHROMIMEMSGSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SYNCHROMIMEMSGSID, 0); - pYAMNMailFcn->TranslateHeaderFcn = (YAMN_TRANSLATEHEADERFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_TRANSLATEHEADERID, 0); - pYAMNMailFcn->AppendQueueFcn = (YAMN_APPENDQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_APPENDQUEUEID, 0); - pYAMNMailFcn->DeleteMessagesToEndFcn = (YAMN_DELETEMIMEQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_DELETEMIMEQUEUEID, 0); - pYAMNMailFcn->DeleteMessageFromQueueFcn = (YAMN_DELETEMIMEMESSAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_DELETEMIMEMESSAGEID, 0); - pYAMNMailFcn->FindMessageByIDFcn = (YAMN_FINDMIMEMESSAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_FINDMIMEMESSAGEID, 0); - pYAMNMailFcn->CreateNewDeleteQueueFcn = (YAMN_CREATENEWDELETEQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_CREATENEWDELETEQUEUEID, 0); - - //set static variable - if (CPOP3Account::AccountWriterSO == nullptr) - if (nullptr == (CPOP3Account::AccountWriterSO = new SCOUNTER)) - goto LBL_Error; - - //First, we register this plugin - //it is quite impossible this function returns zero (failure) as YAMN and internal plugin structre versions are the same - POP3ProtocolRegistration.Name = Translate("POP3 protocol (internal)"); - POP3ProtocolRegistration.Description = Translate(__DESCRIPTION); - if (nullptr == (POP3Plugin = (HYAMNPROTOPLUGIN)CallService(MS_YAMN_REGISTERPROTOPLUGIN, (WPARAM)&POP3ProtocolRegistration, (LPARAM)YAMN_PROTOREGISTRATIONVERSION))) - return 0; - - //Next we set our imported functions for YAMN - if (!SetProtocolPluginFcnImport(POP3Plugin, &POP3ProtocolFunctions, YAMN_PROTOIMPORTFCNVERSION, &POP3MailFunctions, YAMN_MAILIMPORTFCNVERSION)) - return 0; - - //Then, we read all mails for accounts. - //You must first register account, before using this function as YAMN must use CreatePOP3Account function to add new accounts - //But if CreatePOP3Account is not implemented (equals to NULL), YAMN creates account as YAMN's standard CAccount * - if (FileName) CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); //shoud not happen (only for secure) - FileName = (wchar_t *)CallService(MS_YAMN_GETFILENAME, (WPARAM)L"pop3", 0); - - switch (CallService(MS_YAMN_READACCOUNTS, (WPARAM)POP3Plugin, (LPARAM)FileName)) { - case EACC_FILEVERSION: - MessageBox(nullptr, TranslateT("Found new version of account book, not compatible with this version of YAMN."), TranslateT("YAMN (internal POP3) read error"), MB_OK); - CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); - FileName = nullptr; - return 0; - case EACC_FILECOMPATIBILITY: - MessageBox(nullptr, TranslateT("Error reading account file. Account file corrupted."), TranslateT("YAMN (internal POP3) read error"), MB_OK); - CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); - FileName = nullptr; - return 0; - case EACC_ALLOC: - MessageBox(nullptr, TranslateT("Memory allocation error while data reading"), TranslateT("YAMN (internal POP3) read error"), MB_OK); - CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); - FileName = nullptr; - return 0; - case EACC_SYSTEM: - if (ERROR_FILE_NOT_FOUND != GetLastError()) { - wchar_t temp[1024] = {0}; - mir_snwprintf(temp, L"%s\n%s", TranslateT("Reading file error. File already in use?"), FileName); - MessageBox(nullptr, temp, TranslateT("YAMN (internal POP3) read error"), MB_OK); - CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); - FileName = nullptr; - return 0; - } - break; - } - - for (CAccount *Finder = POP3Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { - Finder->hContact = NULL; - for (auto &hContact : Contacts(YAMN_DBMODULE)) { - DBVARIANT dbv; - if (!g_plugin.getString(hContact, "Id", &dbv)) { - if (mir_strcmp(dbv.pszVal, Finder->Name) == 0) { - Finder->hContact = hContact; - g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_ONLINE); - db_set_s(Finder->hContact, "CList", "StatusMsg", Translate("No new mail message")); - if ((Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) - Contact::Hide(Finder->hContact, false); - - if (!(Finder->Flags & YAMN_ACC_ENA) || !(Finder->NewMailN.Flags & YAMN_ACC_CONT)) - Contact::Hide(Finder->hContact); - } - db_free(&dbv); - } - } - - if (!Finder->hContact && (Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) { - // No account contact found, have to create one - Finder->hContact = db_add_contact(); - Proto_AddToContact(Finder->hContact, YAMN_DBMODULE); - g_plugin.setString(Finder->hContact, "Id", Finder->Name); - g_plugin.setString(Finder->hContact, "Nick", Finder->Name); - g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_OFFLINE); - } - } - - return 0; -} - -DWORD WINAPI UnLoadPOP3(void *) -{ - //pYAMNVar is only a pointr, no need delete or free - if (hNetLib) { - Netlib_CloseHandle(hNetLib); hNetLib = nullptr; - } - if (CPOP3Account::AccountWriterSO) { - delete CPOP3Account::AccountWriterSO; CPOP3Account::AccountWriterSO = nullptr; - } - if (pYAMNMailFcn) { - delete pYAMNMailFcn; pYAMNMailFcn = nullptr; - } - if (pYAMNFcn) { - delete pYAMNFcn; pYAMNFcn = nullptr; - } - if (FileName) { - CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); FileName = nullptr; - } - - return 1; -} - -DWORD WINAPI WritePOP3Accounts() -{ - uint32_t ReturnValue = CallService(MS_YAMN_WRITEACCOUNTS, (WPARAM)POP3Plugin, (LPARAM)FileName); - if (ReturnValue == EACC_SYSTEM) { - wchar_t temp[1024] = {0}; - mir_snwprintf(temp, L"%s\n%s", TranslateT("Error while copying data to disk occurred. Is file in use?"), FileName); - MessageBox(nullptr, temp, TranslateT("POP3 plugin - write file error"), MB_OK); - } - - return ReturnValue; -} - -DWORD WINAPI WritePOP3Options(HANDLE File, CAccount *Which) -{ - DWORD WrittenBytes; - uint32_t Ver = POP3_FILEVERSION; - - if ((!WriteFile(File, (char *)&Ver, sizeof(uint32_t), &WrittenBytes, nullptr)) || - (!WriteFile(File, (char *)&((HPOP3ACCOUNT)Which)->CP, sizeof(uint16_t), &WrittenBytes, nullptr))) - return EACC_SYSTEM; - return 0; -} - -DWORD WINAPI ReadPOP3Options(CAccount *Which, char **Parser, char *End) -{ - uint32_t Ver; - #ifdef DEBUG_FILEREAD - wchar_t Debug[256]; - #endif - Ver = *(uint32_t *)(*Parser); - (*Parser) += sizeof(uint32_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - if (Ver != POP3_FILEVERSION) - return EACC_FILECOMPATIBILITY; - - ((HPOP3ACCOUNT)Which)->CP = *(uint16_t *)(*Parser); - (*Parser) += sizeof(uint16_t); - if (*Parser >= End) - return EACC_FILECOMPATIBILITY; - #ifdef DEBUG_FILEREAD - mir_snwprintf(Debug, L"CodePage: %d, remaining %d chars", ((HPOP3ACCOUNT)Which)->CP, End - *Parser); - MessageBox(NULL, Debug, L"debug", MB_OK); - #endif - return 0; -} - -HYAMNMAIL WINAPI CreatePOP3Mail(CAccount *Account, DWORD) -{ - HYAMNMAIL NewMail; - //First, we should check whether MAILDATA matches. - //But this is internal plugin, so YAMN's MAILDATA structure and our MAILDATA structure are - //the same, so we do not need to test version. Otherwise, if MAILDATA version does not match - //in your plugin, you should return NULL, like this: - // if (MailDataVersion != YAMN_MAILDATAVERSION) return NULL; - - //Now it is needed to construct our POP3 account and return its handle - if (nullptr == (NewMail = new YAMNMAIL)) - return nullptr; - - if (nullptr == (NewMail->MailData = new CMailData())) { - delete NewMail; - return nullptr; - } - NewMail->MailData->CP = ((HPOP3ACCOUNT)Account)->CP; - return (HYAMNMAIL)NewMail; -} - -static void SetContactStatus(CAccount *account, int status) -{ - if ((account->hContact) && (account->NewMailN.Flags & YAMN_ACC_CONT)) - g_plugin.setWord(account->hContact, "Status", status); -} - -static void PostErrorProc(HPOP3ACCOUNT ActualAccount, void *ParamToBadConnection, uint32_t POP3PluginParam, BOOL UseSSL) -{ - char *DataRX; - - //We create new structure, that we pass to bad connection dialog procedure. This procedure next calls YAMN imported fuction - //from POP3 protocol to determine the description of error. We can describe error from our error code structure, because later, - //when YAMN calls our function, it passes us our error code. This is pointer to structure for POP3 protocol in fact. - PPOP3_ERRORCODE ErrorCode; - - //We store status before we do Quit(), because quit can destroy our errorcode status - if (nullptr != (ErrorCode = new POP3_ERRORCODE)) { - ErrorCode->SSL = UseSSL; - ErrorCode->AppError = ActualAccount->SystemError; - ErrorCode->POP3Error = ActualAccount->Client.POP3Error; - ErrorCode->NetError = ActualAccount->Client.NetClient->NetworkError; - ErrorCode->SystemError = ActualAccount->Client.NetClient->SystemError; - } - - if (POP3PluginParam == (uint32_t)NULL) //if it was normal YAMN call (force check or so on) - { - try { - DataRX = ActualAccount->Client.Quit(); - if (DataRX != nullptr) - free(DataRX); - } - catch (...) { - } - //We always close connection if error occured - try { - ActualAccount->Client.NetClient->Disconnect(); - } - catch (...) { - } - - SetAccountStatus(ActualAccount, TranslateT("Disconnected")); - - //If we cannot allocate memory, do nothing - if (ErrorCode == nullptr) { - SetEvent(ActualAccount->UseInternetFree); - return; - } - } - else // else it was called from POP3 plugin, probably error when deleting old mail (POP3 synchro calls POP3 delete) - if (ErrorCode == nullptr) - return; - - if ((ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) || (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) || (ActualAccount->BadConnectN.Flags & YAMN_ACC_POP)) - RunBadConnection(ActualAccount, (UINT_PTR)ErrorCode, ParamToBadConnection); - - if (POP3PluginParam == (uint32_t)NULL) //if it was normal YAMN call - SetEvent(ActualAccount->UseInternetFree); -} - -//Checks POP3 account and synchronizes it -DWORD WINAPI SynchroPOP3(struct CheckParam *WhichTemp) -{ - HPOP3ACCOUNT ActualAccount; - CPop3Client *MyClient; - HYAMNMAIL NewMails = nullptr, MsgQueuePtr = nullptr; - char *DataRX = nullptr, *Temp; - int mboxsize, msgs, i; - SYSTEMTIME now; - LPVOID YAMNParam; - uint32_t CheckFlags; - BOOL UsingInternet = FALSE; - struct - { - char *ServerName; - uint32_t ServerPort; - char *ServerLogin; - char *ServerPasswd; - uint32_t Flags; - uint32_t NFlags; - uint32_t NNFlags; - } ActualCopied; - - //First, we should compare our version of CheckParam structure, but here it is not needed, because YAMN and internal plugin - //have the same version. But your plugin should do that in this way: - // if (((struct CheckParam *)WhichTemp)->Ver != YAMN_CHECKVERSION) - // { - // SetEvent(((struct CheckParam *)WhichTemp)->ThreadRunningEV); //don't forget to unblock YAMN - // return (uint32_t)-1; //ok, but we should return value. - // //When our plugin returns e.g. 0xFFFFFFFF (=-1, this is only our plugin value, YAMN does nothing with return value, - // //but only tests if it is nonzero. If yes, it calls GetErrorStringFcn. We know problem occured in YAMN incompatibility - // //and then we can in our GetErrorStringFcn e.g. return string "Uncompatible version of YAMN". - // } - - ActualAccount = (HPOP3ACCOUNT)WhichTemp->AccountParam; //copy address of structure from calling thread to stack of this thread - YAMNParam = WhichTemp->BrowserParam; - CheckFlags = WhichTemp->Flags; - - SCInc(ActualAccount->UsingThreads); - //Unblock YAMN, signal that we have copied all parameters from YAMN thread stack - if (INVALID_HANDLE_VALUE != WhichTemp->ThreadRunningEV) - SetEvent(WhichTemp->ThreadRunningEV); - - if (WAIT_OBJECT_0 != WaitToRead(ActualAccount)) { - SCDec(ActualAccount->UsingThreads); - return 0; - } - - MyClient = &ActualAccount->Client; - //Now, copy all needed information about account to local variables, so ActualAccount is not blocked in read mode during all connection process, which can last for several minutes. - ActualCopied.ServerName = _strdup(ActualAccount->Server->Name); - ActualCopied.ServerPort = ActualAccount->Server->Port; - ActualCopied.Flags = ActualAccount->Flags; - ActualCopied.ServerLogin = _strdup(ActualAccount->Server->Login); - ActualCopied.ServerPasswd = _strdup(ActualAccount->Server->Passwd); - ActualCopied.NFlags = ActualAccount->NewMailN.Flags; - ActualCopied.NNFlags = ActualAccount->NoNewMailN.Flags; - - ReadDone(ActualAccount); - SCInc(ActualAccount->InternetQueries); //increment counter, that there is one more thread waiting for connection - - WaitForSingleObject(ActualAccount->UseInternetFree, INFINITE); //wait until we can use connection - SCDec(ActualAccount->InternetQueries); - - //OK, we enter the "use internet" section. But after we start communication, we can test if we did not enter the "use internet" section only for the reason, - //that previous thread release the internet section because this account has stop signal (we stop account and there are 2 threads: one communicating, - //the second one waiting for network access- the first one ends because we want to stop account, this one is released, but should be stopped as well). - if (!ActualAccount->AbleToWork) { - SetEvent(ActualAccount->UseInternetFree); - SCDec(ActualAccount->UsingThreads); - return 0; - } - UsingInternet = TRUE; - - GetLocalTime(&now); - ActualAccount->SystemError = 0; //now we can use internet for this socket. First, clear errorcode. - try { - SetContactStatus(ActualAccount, ID_STATUS_OCCUPIED); - - // if we are already connected, we have open session (another thread left us open session), so we don't need to login - // note that connected state without logging cannot occur, because if we close session, we always close socket too (we must close socket is the right word :)) - if ((MyClient->NetClient == nullptr) || !MyClient->NetClient->Connected()) { - SetAccountStatus(ActualAccount, TranslateT("Connecting to server")); - - DataRX = MyClient->Connect(ActualCopied.ServerName, ActualCopied.ServerPort, ActualCopied.Flags & YAMN_ACC_SSL23, ActualCopied.Flags & YAMN_ACC_NOTLS); - char *timestamp = nullptr; - - if (DataRX != nullptr) { - if (ActualCopied.Flags & YAMN_ACC_APOP) { - char *lpos = strchr(DataRX, '<'); - char *rpos = strchr(DataRX, '>'); - if (lpos && rpos && rpos > lpos) { - int sz = (int)(rpos - lpos + 2); - timestamp = new char[sz]; - memcpy(timestamp, lpos, sz - 1); - timestamp[sz - 1] = '\0'; - } - } - free(DataRX); - DataRX = nullptr; - } - - SetAccountStatus(ActualAccount, TranslateT("Entering POP3 account")); - - if (ActualCopied.Flags & YAMN_ACC_APOP) { - DataRX = MyClient->APOP(ActualCopied.ServerLogin, ActualCopied.ServerPasswd, timestamp); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - delete[] timestamp; - } - else { - DataRX = MyClient->User(ActualCopied.ServerLogin); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - DataRX = MyClient->Pass(ActualCopied.ServerPasswd); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - } - } - SetAccountStatus(ActualAccount, TranslateT("Searching for new mail message")); - - DataRX = MyClient->Stat(); - - ExtractStat(DataRX, &mboxsize, &msgs); - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - for (i = 0; i < msgs; i++) { - if (!i) - MsgQueuePtr = NewMails = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); - else { - MsgQueuePtr->Next = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); - MsgQueuePtr = MsgQueuePtr->Next; - } - if (MsgQueuePtr == nullptr) { - ActualAccount->SystemError = EPOP3_QUEUEALLOC; - throw (uint32_t)ActualAccount->SystemError; - } - } - - if (msgs) { - DataRX = MyClient->List(); - ExtractList(DataRX, MyClient->NetClient->Rcv, NewMails); - if (DataRX != nullptr) - free(DataRX); - - DataRX = MyClient->Uidl(); - ExtractUIDL(DataRX, MyClient->NetClient->Rcv, NewMails); - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - } - - if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) - throw (uint32_t)(ActualAccount->SystemError = EACC_STOPPED); - - ActualAccount->LastChecked = now; - for (MsgQueuePtr = (HYAMNMAIL)ActualAccount->Mails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next) { - if (MsgQueuePtr->Flags & YAMN_MSG_BODYREQUESTED) { - HYAMNMAIL NewMsgsPtr = nullptr; - for (NewMsgsPtr = (HYAMNMAIL)NewMails; NewMsgsPtr != nullptr; NewMsgsPtr = NewMsgsPtr->Next) { - if (!mir_strcmp(MsgQueuePtr->ID, NewMsgsPtr->ID)) { - wchar_t accstatus[512]; - mir_snwprintf(accstatus, TranslateT("Reading body %s"), NewMsgsPtr->ID); - SetAccountStatus(ActualAccount, accstatus); - DataRX = MyClient->Top(MsgQueuePtr->Number, 100); - if (DataRX != nullptr) { - Temp = DataRX; - while ((Temp < DataRX + MyClient->NetClient->Rcv) && (WS(Temp) || ENDLINE(Temp))) Temp++; - - if (OKLINE(DataRX)) - for (Temp = DataRX; (Temp < DataRX + MyClient->NetClient->Rcv) && (!ENDLINE(Temp)); Temp++); - while ((Temp < DataRX + MyClient->NetClient->Rcv) && ENDLINE(Temp)) Temp++; - } - else - continue; - //delete all the headers of the old mail MsgQueuePtr->MailData->TranslatedHeader - struct CMimeItem *TH = MsgQueuePtr->MailData->TranslatedHeader; - if (TH) for (; MsgQueuePtr->MailData->TranslatedHeader != nullptr;) { - TH = TH->Next; - if (MsgQueuePtr->MailData->TranslatedHeader->name != nullptr) - delete[] MsgQueuePtr->MailData->TranslatedHeader->name; - if (MsgQueuePtr->MailData->TranslatedHeader->value != nullptr) - delete[] MsgQueuePtr->MailData->TranslatedHeader->value; - delete MsgQueuePtr->MailData->TranslatedHeader; - MsgQueuePtr->MailData->TranslatedHeader = TH; - } - - TranslateHeader(Temp, MyClient->NetClient->Rcv - (Temp - DataRX), &MsgQueuePtr->MailData->TranslatedHeader); - - MsgQueuePtr->Flags |= YAMN_MSG_BODYRECEIVED; - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - break; - } - } - } - } - - SynchroMessages(ActualAccount, (HYAMNMAIL *)&ActualAccount->Mails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); //we get only new mails on server! - - MsgsWriteDone(ActualAccount); - for (MsgQueuePtr = (HYAMNMAIL)ActualAccount->Mails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next) { - if ((MsgQueuePtr->Flags & YAMN_MSG_BODYREQUESTED) && (MsgQueuePtr->Flags & YAMN_MSG_BODYRECEIVED)) { - MsgQueuePtr->Flags &= ~YAMN_MSG_BODYREQUESTED; - if (MsgQueuePtr->MsgWindow) - SendMessage(MsgQueuePtr->MsgWindow, WM_YAMN_CHANGECONTENT, 0, 0); - } - } - - for (msgs = 0, MsgQueuePtr = NewMails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next, msgs++); //get number of new mails - - try { - wchar_t accstatus[512]; - - for (i = 0, MsgQueuePtr = NewMails; MsgQueuePtr != nullptr; i++) { - BOOL autoretr = (ActualAccount->Flags & YAMN_ACC_BODY) != 0; - DataRX = MyClient->Top(MsgQueuePtr->Number, autoretr ? 100 : 0); - mir_snwprintf(accstatus, TranslateT("Reading new mail messages (%d%% done)"), 100 * i / msgs); - SetAccountStatus(ActualAccount, accstatus); - - if (DataRX != nullptr) { - Temp = DataRX; - while ((Temp < DataRX + MyClient->NetClient->Rcv) && (WS(Temp) || ENDLINE(Temp))) Temp++; - - if (OKLINE(DataRX)) - for (Temp = DataRX; (Temp < DataRX + MyClient->NetClient->Rcv) && (!ENDLINE(Temp)); Temp++); - while ((Temp < DataRX + MyClient->NetClient->Rcv) && ENDLINE(Temp)) Temp++; - } - else - continue; - - TranslateHeader(Temp, MyClient->NetClient->Rcv - (Temp - DataRX), &MsgQueuePtr->MailData->TranslatedHeader); - - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - MsgQueuePtr->Flags |= YAMN_MSG_NORMALNEW; - if (autoretr) MsgQueuePtr->Flags |= YAMN_MSG_BODYRECEIVED; - - //We are going to filter mail. Warning!- we must not be in read access neither write access to mails when calling this service - //This is done, because the "NewMails" queue is not synchronised. It is because it is new queue. Only this thread uses new queue yet, it is not - //connected to account mail queue. - // CallService(MS_YAMN_FILTERMAIL,(WPARAM)ActualAccount,(LPARAM)MsgQueuePtr); - FilterMailSvc((WPARAM)ActualAccount, (LPARAM)MsgQueuePtr); - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - - //MsgQueuePtr->MailData->Body=MyClient->Retr(MsgQueuePtr->Number); - - MsgQueuePtr = MsgQueuePtr->Next; - - } - - if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) - throw (uint32_t)ActualAccount->SystemError == EACC_STOPPED; - - if (ActualAccount->Mails == nullptr) - ActualAccount->Mails = NewMails; - else { - ActualAccount->LastMail = ActualAccount->LastChecked; - AppendQueue((HYAMNMAIL)ActualAccount->Mails, NewMails); - } - - MsgsWriteDone(ActualAccount); - - // we are going to delete mails having SPAM flag level3 and 4 (see m_mails.h) set - { - struct DeleteParam ParamToDeleteMails = {YAMN_DELETEVERSION, INVALID_HANDLE_VALUE, ActualAccount, YAMNParam, (void *)POP3_DELETEFROMCHECK}; - - // Delete mails from server. Here we should not be in write access for account's mails - DeleteMailsPOP3(&ParamToDeleteMails); - } - - // if there is no waiting thread for internet connection close it - // else leave connection open - if (0 == SCGetNumber(ActualAccount->InternetQueries)) { - DataRX = MyClient->Quit(); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - MyClient->NetClient->Disconnect(); - - SetAccountStatus(ActualAccount, TranslateT("Disconnected")); - } - - UsingInternet = FALSE; - SetEvent(ActualAccount->UseInternetFree); - - ActualAccount->LastSChecked = ActualAccount->LastChecked; - ActualAccount->LastSynchronised = ActualAccount->LastChecked; - } - catch (...) { - throw; //go to the main exception handling - } - - { - YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualCopied.NFlags, ActualCopied.NNFlags, YAMNParam}; - - if (CheckFlags & YAMN_FORCECHECK) - Param.nnflags |= YAMN_ACC_POP; //if force check, show popup anyway and if mailbrowser was opened, do not close - Param.nnflags |= YAMN_ACC_MSGP; //do not close browser if already open - CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); - } - SetContactStatus(ActualAccount, ActualAccount->isCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); - } - #ifdef DEBUG_COMM - catch (uint32_t ErrorCode) - #else - catch (uint32_t) - #endif - { - if (ActualAccount->Client.POP3Error == EPOP3_STOPPED) - ActualAccount->SystemError = EACC_STOPPED; - #ifdef DEBUG_COMM - DebugLog(CommFile, "ERROR: %x\n", ErrorCode); - #endif - if (WAIT_OBJECT_0 == MsgsWaitToWrite(ActualAccount)) { - ActualAccount->LastChecked = now; - MsgsWriteDone(ActualAccount); - } - - DeleteMIMEQueue(ActualAccount, NewMails); - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - switch (ActualAccount->SystemError) { - case EACC_QUEUEALLOC: - case EACC_STOPPED: - ActualAccount->Client.NetClient->Disconnect(); - break; - default: - PostErrorProc(ActualAccount, YAMNParam, (uint32_t)NULL, MyClient->SSL); //it closes internet connection too - } - - if (UsingInternet) //if our thread still uses internet - SetEvent(ActualAccount->UseInternetFree); - - SetContactStatus(ActualAccount, ID_STATUS_NA); - } - free(ActualCopied.ServerName); - free(ActualCopied.ServerLogin); - free(ActualCopied.ServerPasswd); - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - - // WriteAccounts(); - SCDec(ActualAccount->UsingThreads); - return 0; -} - -void __cdecl DeleteMailsPOP3(void *param) -{ - DeleteParam *WhichTemp = (DeleteParam *)param; - - CPop3Client *MyClient; - HYAMNMAIL DeleteMails, NewMails = nullptr, MsgQueuePtr = nullptr; - char *DataRX = nullptr; - int mboxsize = 0, msgs = 0, i; - BOOL UsingInternet = FALSE; - struct - { - char *ServerName; - uint32_t ServerPort; - char *ServerLogin; - char *ServerPasswd; - uint32_t Flags; - uint32_t NFlags; - uint32_t NNFlags; - } ActualCopied; - - //First, we should compare our version of DeleteParam structure, but here it is not needed, because YAMN and internal plugin - //have the same version. But your plugin should do that in this way: - // if (((struct DeleteParam *)WhichTemp)->Ver != YAMN_DELETEVERSION) - // { - // SetEvent(((struct DeleteParam *)WhichTemp)->ThreadRunningEV); //don't forget to unblock YAMN - // return (uint32_t)-1; //ok, but we should return value. - // //When our plugin returns e.g. 0xFFFFFFFF (this is only our plugin value, YAMN does nothing with return value, - // //but only tests if it is nonzero. If yes, it calls GetErrorStringFcn), we know problem occured in YAMN incompatibility - // //and then we can in our GetErrorStringFcn e.g. return string "Uncompatible version of YAMN". - // } - - HPOP3ACCOUNT ActualAccount = (HPOP3ACCOUNT)WhichTemp->AccountParam; //copy address of structure from calling thread to stack of this thread - LPVOID YAMNParam = WhichTemp->BrowserParam; - UINT_PTR POP3PluginParam = (UINT_PTR)((struct DeleteParam *)WhichTemp)->CustomParam; - - SCInc(ActualAccount->UsingThreads); - if (INVALID_HANDLE_VALUE != WhichTemp->ThreadRunningEV) - SetEvent(WhichTemp->ThreadRunningEV); - - if (WAIT_OBJECT_0 != WaitToRead(ActualAccount)) { - SCDec(ActualAccount->UsingThreads); - return; - } - - if (nullptr == (DeleteMails = (HYAMNMAIL)CreateNewDeleteQueue((HYAMNMAIL)ActualAccount->Mails))) //if there's no mail for deleting, return - { - if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not wait for free internet when calling from SynchroPOP3. It is because UseInternetFree is blocked - { - YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, YAMN_ACC_MSGP, YAMN_ACC_MSGP, YAMNParam}; //Just update the window - CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); - } - - SCDec(ActualAccount->UsingThreads); - return; - } - - MyClient = &(ActualAccount->Client); - - //Now, copy all needed information about account to local variables, so ActualAccount is not blocked in read mode during all connection process, which can last for several minutes. - ActualCopied.ServerName = _strdup(ActualAccount->Server->Name); - ActualCopied.ServerPort = ActualAccount->Server->Port; - ActualCopied.Flags = ActualAccount->Flags; - ActualCopied.ServerLogin = _strdup(ActualAccount->Server->Login); - ActualCopied.ServerPasswd = _strdup(ActualAccount->Server->Passwd); - ActualCopied.NFlags = ActualAccount->NewMailN.Flags; - ActualCopied.NNFlags = ActualAccount->NoNewMailN.Flags; - - ReadDone(ActualAccount); - - SCInc(ActualAccount->InternetQueries); //This is POP3-internal SCOUNTER, we set another thread wait for this account to be connected to inet - if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not wait for free internet when calling from SynchroPOP3. It is because UseInternetFree is blocked - WaitForSingleObject(ActualAccount->UseInternetFree, INFINITE); - - SCDec(ActualAccount->InternetQueries); - UsingInternet = TRUE; - - try { - SetContactStatus(ActualAccount, ID_STATUS_OCCUPIED); - #ifdef DEBUG_COMM - DebugLog(CommFile, "<--------Communication-------->\n"); - #endif - if ((MyClient->NetClient == nullptr) || !MyClient->NetClient->Connected()) { - SetAccountStatus(ActualAccount, TranslateT("Connecting to server")); - - DataRX = MyClient->Connect(ActualCopied.ServerName, ActualCopied.ServerPort, ActualCopied.Flags & YAMN_ACC_SSL23, ActualCopied.Flags & YAMN_ACC_NOTLS); - - char *timestamp = nullptr; - if (DataRX != nullptr) { - if (ActualAccount->Flags & YAMN_ACC_APOP) { - char *lpos = strchr(DataRX, '<'); - char *rpos = strchr(DataRX, '>'); - if (lpos && rpos && rpos > lpos) { - int sz = (int)(rpos - lpos + 2); - timestamp = new char[sz]; - memcpy(timestamp, lpos, sz - 1); - timestamp[sz - 1] = '\0'; - } - } - free(DataRX); - DataRX = nullptr; - } - SetAccountStatus(ActualAccount, TranslateT("Entering POP3 account")); - - if (ActualAccount->Flags & YAMN_ACC_APOP) { - DataRX = MyClient->APOP(ActualCopied.ServerLogin, ActualCopied.ServerPasswd, timestamp); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - delete[] timestamp; - } - else { - DataRX = MyClient->User(ActualCopied.ServerLogin); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - DataRX = MyClient->Pass(ActualCopied.ServerPasswd); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - } - } - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "<--------Deleting requested mails-------->\n"); - #endif - if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not need to get mails on server as we have already it from check function - { - SetAccountStatus(ActualAccount, TranslateT("Deleting requested mails")); - - DataRX = MyClient->Stat(); - - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - ExtractStat(DataRX, &mboxsize, &msgs); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%d\n", mboxsize); - DebugLog(DecodeFile, "%d\n", msgs); - DebugLog(DecodeFile, "\n"); - #endif - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - for (i = 0; i < msgs; i++) { - if (!i) - MsgQueuePtr = NewMails = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); - else { - MsgQueuePtr->Next = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); - MsgQueuePtr = MsgQueuePtr->Next; - } - if (MsgQueuePtr == nullptr) { - ActualAccount->SystemError = EPOP3_QUEUEALLOC; - throw (uint32_t)ActualAccount->SystemError; - } - } - - if (msgs) { - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - DataRX = MyClient->Uidl(); - ExtractUIDL(DataRX, MyClient->NetClient->Rcv, NewMails); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - // we get "new mails" on server (NewMails will contain all mails on server not found in DeleteMails) - // but also in DeleteMails we get only those, which are still on server with their responsable numbers - SynchroMessages(ActualAccount, (HYAMNMAIL *)&DeleteMails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); - } - } - else SetAccountStatus(ActualAccount, TranslateT("Deleting spam")); - - if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) - throw (uint32_t)EACC_STOPPED; - - if (msgs || POP3_DELETEFROMCHECK == POP3PluginParam) { - try { - HYAMNMAIL Temp; - - for (i = 0, MsgQueuePtr = DeleteMails; MsgQueuePtr != nullptr; i++) { - if (!(MsgQueuePtr->Flags & YAMN_MSG_VIRTUAL)) //of course we can only delete real mails, not virtual - { - DataRX = MyClient->Dele(MsgQueuePtr->Number); - Temp = MsgQueuePtr->Next; - if (POP3_FOK == MyClient->AckFlag) //if server answers that mail was deleted - { - DeleteMIMEMessage((HYAMNMAIL *)&DeleteMails, MsgQueuePtr); - HYAMNMAIL DeletedMail = FindMIMEMessageByID((HYAMNMAIL)ActualAccount->Mails, MsgQueuePtr->ID); - if ((MsgQueuePtr->Flags & YAMN_MSG_MEMDELETE)) //if mail should be deleted from memory (or disk) - { - DeleteMIMEMessage((HYAMNMAIL *)&ActualAccount->Mails, DeletedMail); //remove from queue - CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)POP3Plugin, (LPARAM)DeletedMail); - } - else //else mark it only as "deleted mail" - { - DeletedMail->Flags |= (YAMN_MSG_VIRTUAL | YAMN_MSG_DELETED); - DeletedMail->Flags &= ~(YAMN_MSG_NEW | YAMN_MSG_USERDELETE | YAMN_MSG_AUTODELETE); //clear "new mail" - } - delete MsgQueuePtr->MailData; - delete[] MsgQueuePtr->ID; - delete MsgQueuePtr; - } - MsgQueuePtr = Temp; - - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - } - else - MsgQueuePtr = MsgQueuePtr->Next; - } - } - catch (...) //if any exception in the code where we have write-access to account occured, don't forget to leave write-access - { - MsgsWriteDone(ActualAccount); - throw; //and go to the main exception handling - } - - if (NewMails != nullptr) - // in ActualAccount->Mails we have all mails stored before calling this function - // in NewMails we have all mails not found in DeleteMails (in other words: we performed new ID checking and we - // stored all mails found on server, then we deleted the ones we wanted to delete in this function - // and NewMails queue now contains actual state of mails on server). But we will not use NewMails as actual state, because NewMails does not contain header data (subject, from...) - // We perform deleting from ActualAccount->Mails: we remove from original queue (ActualAccount->Mails) all deleted mails - SynchroMessages(ActualAccount, (HYAMNMAIL *)&ActualAccount->Mails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); - // Now ActualAccount->Mails contains all mails when calling this function except the ones, we wanted to delete (these are in DeleteMails) - // And in NewMails we have new mails (if any) - else if (POP3_DELETEFROMCHECK != POP3PluginParam) { - DeleteMIMEQueue(ActualAccount, (HYAMNMAIL)ActualAccount->Mails); - ActualAccount->Mails = nullptr; - } - } - else { - DeleteMIMEQueue(ActualAccount, (HYAMNMAIL)ActualAccount->Mails); - ActualAccount->Mails = nullptr; - } - - MsgsWriteDone(ActualAccount); - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - - // TODO: now, we have in NewMails new mails. If NewMails is not NULL, we found some new mails, so Checking for new mail should be performed - // now, we do not call CheckPOP3 - - // if there is no waiting thread for internet connection close it - // else leave connection open - // if this functin was called from SynchroPOP3, then do not try to disconnect - if (POP3_DELETEFROMCHECK != POP3PluginParam) { - YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualCopied.NFlags, YAMN_ACC_MSGP, YAMNParam}; - - CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); - - if (0 == SCGetNumber(ActualAccount->InternetQueries)) { - DataRX = MyClient->Quit(); - if (DataRX != nullptr) - free(DataRX); - DataRX = nullptr; - MyClient->NetClient->Disconnect(); - - SetAccountStatus(ActualAccount, TranslateT("Disconnected")); - } - - UsingInternet = FALSE; - SetEvent(ActualAccount->UseInternetFree); - } - SetContactStatus(ActualAccount, ActualAccount->isCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); - } - #ifdef DEBUG_COMM - catch (uint32_t ErrorCode) - #else - catch (uint32_t) - #endif - { - if (ActualAccount->Client.POP3Error == EPOP3_STOPPED) - ActualAccount->SystemError = EACC_STOPPED; - #ifdef DEBUG_COMM - DebugLog(CommFile, "ERROR %x\n", ErrorCode); - #endif - if (DataRX != nullptr) - free(DataRX); - switch (ActualAccount->SystemError) { - case EACC_QUEUEALLOC: - case EACC_STOPPED: - ActualAccount->Client.NetClient->Disconnect(); - break; - default: - PostErrorProc(ActualAccount, YAMNParam, POP3PluginParam, MyClient->SSL); //it closes internet connection too - } - - if (UsingInternet && (POP3_DELETEFROMCHECK != POP3PluginParam)) //if our thread still uses internet and it is needed to release internet - SetEvent(ActualAccount->UseInternetFree); - } - - free(ActualCopied.ServerName); - free(ActualCopied.ServerLogin); - free(ActualCopied.ServerPasswd); - - DeleteMIMEQueue(ActualAccount, NewMails); - DeleteMIMEQueue(ActualAccount, DeleteMails); - - #ifdef DEBUG_COMM - DebugLog(CommFile, "\n"); - #endif - - // WriteAccounts(); - SCDec(ActualAccount->UsingThreads); - return; -} - -void ExtractStat(char *stream, int *mboxsize, int *mails) -{ - char *finder = stream; - while (WS(finder) || ENDLINE(finder)) finder++; - if (ACKLINE(finder)) { - while (!WS(finder)) finder++; - while (WS(finder)) finder++; - } - if (1 != sscanf(finder, "%d", mails)) - throw (uint32_t)EPOP3_STAT; - while (!WS(finder)) finder++; - while (WS(finder)) finder++; - if (1 != sscanf(finder, "%d", mboxsize)) - throw (uint32_t)EPOP3_STAT; -} -void ExtractMail(char *stream, int len, HYAMNMAIL queue) -{ - char *finder = stream; - char *finderend; - int msgnr, i; - HYAMNMAIL queueptr = queue; - - while (WS(finder) || ENDLINE(finder)) finder++; - while (!ACKLINE(finder)) finder++; - while (!ENDLINE(finder)) finder++; //now we at the end of first ack line - while (finder <= (stream + len)) { - while (ENDLINE(finder)) finder++; //go to the new line - if (DOTLINE(finder + 1)) //at the end of stream - break; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - while (WS(finder)) finder++; //jump whitespace - if (1 != sscanf(finder, "%d", &msgnr)) - throw (uint32_t)EPOP3_UIDL; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%d\n", msgnr); - #endif - // for (i=1,queueptr=queue;(queueptr->Next != NULL) && (iNext,i++); - // if (i != msgnr) - // throw (uint32_t)EPOP3_UIDL; - while (!WS(finder)) finder++; //jump characters - while (WS(finder)) finder++; //jump whitespace - finderend = finder + 1; - while (!WS(finderend) && !ENDLINE(finderend)) finderend++; - queueptr->ID = new char[finderend - finder + 1]; - for (i = 0; finder != finderend; finder++, i++) - queueptr->MailData->Body[i] = *finder; - queueptr->MailData->Body[i] = 0; //ends string - queueptr->Number = msgnr; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%s\n", queueptr->MailData->Body); - DebugLog(DecodeFile, "\n"); - #endif - queueptr = queueptr->Next; - while (!ENDLINE(finder)) finder++; - } -} - -void ExtractUIDL(char *stream, int len, HYAMNMAIL queue) -{ - char *finder = stream; - char *finderend; - int msgnr, i; - HYAMNMAIL queueptr = queue; - - while (WS(finder) || ENDLINE(finder)) finder++; - while (!ACKLINE(finder)) finder++; - while (!ENDLINE(finder)) finder++; //now we at the end of first ack line - while (finder <= (stream + len)) { - while (ENDLINE(finder)) finder++; //go to the new line - if (DOTLINE(finder + 1)) //at the end of stream - break; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n"); - #endif - while (WS(finder)) finder++; //jump whitespace - if (1 != sscanf(finder, "%d", &msgnr)) - throw (uint32_t)EPOP3_UIDL; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%d\n", msgnr); - #endif - // for (i=1,queueptr=queue;(queueptr->Next != NULL) && (iNext,i++); - // if (i != msgnr) - // throw (uint32_t)EPOP3_UIDL; - while (!WS(finder)) finder++; //jump characters - while (WS(finder)) finder++; //jump whitespace - finderend = finder + 1; - while (!WS(finderend) && !ENDLINE(finderend)) finderend++; - queueptr->ID = new char[finderend - finder + 1]; - for (i = 0; finder != finderend; finder++, i++) - queueptr->ID[i] = *finder; - queueptr->ID[i] = 0; //ends string - queueptr->Number = msgnr; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%s\n", queueptr->ID); - DebugLog(DecodeFile, "\n"); - #endif - queueptr = queueptr->Next; - while (!ENDLINE(finder)) finder++; - } -} - -void ExtractList(char *stream, int len, HYAMNMAIL queue) -{ - char *finder = stream; - char *finderend; - int msgnr, i; - HYAMNMAIL queueptr; - - while (WS(finder) || ENDLINE(finder)) finder++; - while (!ACKLINE(finder)) finder++; - while (!ENDLINE(finder)) finder++; //now we at the end of first ack line - while (finder <= (stream + len)) { - while (ENDLINE(finder)) finder++; //go to the new line - if (DOTLINE(finder + 1)) //at the end of stream - break; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "\n", NULL, 0); - #endif - while (WS(finder)) finder++; //jump whitespace - if (1 != sscanf(finder, "%d", &msgnr)) //message nr. - throw (uint32_t)EPOP3_LIST; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%d\n", msgnr); - #endif - - for (i = 1, queueptr = queue; (queueptr->Next != nullptr) && (i < msgnr); queueptr = queueptr->Next, i++); - if (i != msgnr) - throw (uint32_t)EPOP3_LIST; - while (!WS(finder)) finder++; //jump characters - while (WS(finder)) finder++; //jump whitespace - finderend = finder + 1; - if (1 != sscanf(finder, "%u", &queueptr->MailData->Size)) - throw (uint32_t)EPOP3_LIST; - #ifdef DEBUG_DECODE - DebugLog(DecodeFile, "%d\n", queueptr->MailData->Size); - #endif - while (!ENDLINE(finder)) finder++; - } -} - -wchar_t *WINAPI GetErrorString(DWORD Code) -{ - static wchar_t *POP3Errors[] = - { - LPGENW("Memory allocation error."), //memory allocation - LPGENW("Account is about to be stopped."), //stop account - LPGENW("Cannot connect to POP3 server."), - LPGENW("Cannot allocate memory for received data."), - LPGENW("Cannot login to POP3 server."), - LPGENW("Bad user or password."), - LPGENW("Server does not support APOP authorization."), - LPGENW("Error while executing POP3 command."), - LPGENW("Error while executing POP3 command."), - LPGENW("Error while executing POP3 command."), - }; - - static wchar_t *NetlibErrors[] = - { - LPGENW("Cannot connect to server with NetLib."), - LPGENW("Cannot send data."), - LPGENW("Cannot receive data."), - LPGENW("Cannot allocate memory for received data."), - }; - - static wchar_t *SSLErrors[] = - { - LPGENW("OpenSSL not loaded."), - LPGENW("Windows socket 2.0 init failed."), - LPGENW("DNS lookup error."), - LPGENW("Error while creating base socket."), - LPGENW("Error connecting to server with socket."), - LPGENW("Error while creating SSL structure."), - LPGENW("Error connecting socket with SSL."), - LPGENW("Server rejected connection with SSL."), - LPGENW("Cannot write SSL data."), - LPGENW("Cannot read SSL data."), - LPGENW("Cannot allocate memory for received data."), - }; - - wchar_t *ErrorString = new wchar_t[ERRORSTR_MAXLEN]; - POP3_ERRORCODE *ErrorCode = (POP3_ERRORCODE *)(UINT_PTR)Code; - - mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, TranslateT("Error %d-%d-%d-%d:"), ErrorCode->AppError, ErrorCode->POP3Error, ErrorCode->NetError, ErrorCode->SystemError); - if (ErrorCode->POP3Error) - mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(POP3Errors[ErrorCode->POP3Error - 1])); - if (ErrorCode->NetError) { - if (ErrorCode->SSL) - mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(SSLErrors[ErrorCode->NetError - 1])); - else - mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(NetlibErrors[ErrorCode->NetError - 4])); - } - - return ErrorString; -} - -void WINAPI DeleteErrorString(LPVOID String) -{ - delete (char *)String; -} +/* + * This code implements POP3 server checking for new mail and so on. + * There's function SynchroPOP3 in this file- for checking and synchronising POP3 account + * and DeleteMailsPOP3- for deleting mails from POP3 server + * + * Note this file acts as main file for internal plugin. + * + * (c) majvan 2002-2004 + * 18/08 + */ + + +#include "../../stdafx.h" + +#define ERRORSTR_MAXLEN 1024 //in wide-chars + + //-------------------------------------------------------------------------------------------------- + +HANDLE hNetLib = nullptr; +PSCOUNTER CPOP3Account::AccountWriterSO = nullptr; + +//Creates new CPOP3Account structure +CAccount *WINAPI CreatePOP3Account(HYAMNPROTOPLUGIN Plugin, DWORD CAccountVersion); + +//Deletes CPOP3Account structure +void WINAPI DeletePOP3Account(CAccount *Which); + +//Sets stop flag to account +void WINAPI StopPOP3Account(CAccount *Which); + +//Function registers standard functions for YAMN +int RegisterPOP3Plugin(WPARAM, LPARAM); + +//Unloads all variables created on heap (delete[]) +DWORD WINAPI UnLoadPOP3(void *); + +//Function writes POP3 accounts using YAMN exported functions +DWORD WINAPI WritePOP3Accounts(); + +//Function stores plugin's data for account to file +DWORD WINAPI WritePOP3Options(HANDLE, CAccount *); + +//Function reads plugin's data for account from file +DWORD WINAPI ReadPOP3Options(CAccount *, char **, char *); + +//Creates new mail for an account +HYAMNMAIL WINAPI CreatePOP3Mail(CAccount *Account, DWORD CMimeMailVersion); + +//Function does all needed work when connection failed or any error occured +//Creates structure containing error code, closes internet session, runs "bad connect" function +static void PostErrorProc(HPOP3ACCOUNT ActualAccount, void *ParamToBadConnect, uint32_t POP3PluginParam, BOOL UseSSL); + +//Checks POP3 account and stores all info to account. It deletes old mails=> synchro +// WhichTemp- pointer to strucure containing needed information +DWORD WINAPI SynchroPOP3(struct CheckParam *WhichTemp); + +//Deletes mails from POP3 server +// WhichTemp- structure containing needed information (queued messages to delete) +//Function deletes from memory queue in WhichTemp structure +void __cdecl DeleteMailsPOP3(void *param); + +//Function makes readable message about error. It sends it back to YAMN, so YAMN then +//can show it to the message window +wchar_t *WINAPI GetErrorString(DWORD Code); + +//Function deletes string allocated in GetErrorString +void WINAPI DeleteErrorString(LPVOID String); + +//Extracts info from result of POP3's STAT command +// stream- source string +// len- length of source string +// mboxsize- adreess to integer, that receives size of mailbox +// mails- adreess to integer, that receives number of mails +void ExtractStat(char *stream, int *mboxsize, int *mails); + +//Extracts mail ID on mailbox +// stream- source string +// len- length of source string +// queue- address of first message, where first ID will be stored +void ExtractUIDL(char *stream, int len, HYAMNMAIL queue); + +//Extracts mail size on mailbox +// stream- source string +// len- length of source string +// queue- address of first message, where size of message #1 will be stored +void ExtractList(char *stream, int len, HYAMNMAIL queue); + +void ExtractMail(char *stream, int len, HYAMNMAIL queue); + +YAMNExportedFcns *pYAMNFcn = nullptr; +MailExportedFcns *pYAMNMailFcn = nullptr; + +YAMN_PROTOIMPORTFCN POP3ProtocolFunctions = +{ + CreatePOP3Account, + DeletePOP3Account, + StopPOP3Account, + WritePOP3Options, + ReadPOP3Options, + SynchroPOP3, + SynchroPOP3, + SynchroPOP3, + DeleteMailsPOP3, + GetErrorString, + nullptr, + DeleteErrorString, + WritePOP3Accounts, + nullptr, + UnLoadPOP3, +}; + +YAMN_MAILIMPORTFCN POP3MailFunctions = +{ + CreatePOP3Mail, + nullptr, + nullptr, + nullptr, +}; + +PYAMN_VARIABLES pYAMNVar = nullptr; +HYAMNPROTOPLUGIN POP3Plugin = nullptr; + +YAMN_PROTOREGISTRATION POP3ProtocolRegistration = +{ + "POP3 protocol (internal)", + __VERSION_STRING_DOTS, + __COPYRIGHT, + __DESCRIPTION, + __AUTHORWEB, +}; + +static wchar_t *FileName = nullptr; + +HANDLE RegisterNLClient(char *name); + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +CPOP3Account::CPOP3Account() +{ + //NOTE! This constructor constructs CAccount structure. If your plugin is not internal, + //you will need these constructors. All you need is in Account.cpp. Just copy to your source code + //constructor and destructor of CAccount. + UseInternetFree = CreateEvent(nullptr, FALSE, TRUE, nullptr); + InternetQueries = new SCOUNTER; + AbilityFlags = YAMN_ACC_BROWSE | YAMN_ACC_POPUP; + + SetAccountStatus((CAccount *)this, TranslateT("Disconnected")); +} + +CPOP3Account::~CPOP3Account() +{ + CloseHandle(UseInternetFree); + if (InternetQueries != nullptr) + delete InternetQueries; +} + +CAccount *WINAPI CreatePOP3Account(HYAMNPROTOPLUGIN, DWORD) +{ + //First, we should check whether CAccountVersion matches. + //But this is internal plugin, so YAMN's CAccount structure and our CAccount structure are + //the same, so we do not need to test version. Otherwise, if CAccount version does not match + //in your plugin, you should return NULL, like this: + // if (CAccountVersion != YAMN_ACCOUNTVERSION) return NULL; + + //Now it is needed to construct our POP3 account and return its handle + return (CAccount *)new struct CPOP3Account(); +} + +void WINAPI DeletePOP3Account(CAccount *Which) +{ + delete (HPOP3ACCOUNT)Which; +} + +void WINAPI StopPOP3Account(CAccount *Which) +{ + ((HPOP3ACCOUNT)Which)->Client.Stopped = TRUE; + if (((HPOP3ACCOUNT)Which)->Client.NetClient != nullptr) //we should inform also network client. Usefull only when network client implements this feature + ((HPOP3ACCOUNT)Which)->Client.NetClient->Stopped = TRUE; +} + +//This function is like main function for POP3 internal protocol +int RegisterPOP3Plugin(WPARAM, LPARAM) +{ + // Get YAMN variables we can use + if (nullptr == (pYAMNVar = (PYAMN_VARIABLES)CallService(MS_YAMN_GETVARIABLES, YAMN_VARIABLESVERSION, 0))) + return 0; + + // We have to get pointers to YAMN exported functions: allocate structure and fill it + if (nullptr == (pYAMNFcn = new struct YAMNExportedFcns)) { +LBL_Error: + UnLoadPOP3(nullptr); + return 0; + } + + // Register new pop3 user in netlib + if (nullptr == (hNetLib = RegisterNLClient("YAMN (POP3)"))) + goto LBL_Error; + + pYAMNFcn->SetProtocolPluginFcnImportFcn = (YAMN_SETPROTOCOLPLUGINFCNIMPORTFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SETPROTOCOLPLUGINFCNIMPORTID, 0); + pYAMNFcn->WaitToWriteFcn = (YAMN_WAITTOWRITEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WAITTOWRITEID, 0); + pYAMNFcn->WriteDoneFcn = (YAMN_WRITEDONEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WRITEDONEID, 0); + pYAMNFcn->WaitToReadFcn = (YAMN_WAITTOREADFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_WAITTOREADID, 0); + pYAMNFcn->ReadDoneFcn = (YAMN_READDONEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_READDONEID, 0); + pYAMNFcn->SCGetNumberFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCGETNUMBERID, 0); + pYAMNFcn->SCIncFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCINCID, 0); + pYAMNFcn->SCDecFcn = (YAMN_SCMANAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SCDECID, 0); + pYAMNFcn->SetStatusFcn = (YAMN_SETSTATUSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SETSTATUSID, 0); + pYAMNFcn->GetStatusFcn = (YAMN_GETSTATUSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_GETSTATUSID, 0); + + if (nullptr == (pYAMNMailFcn = new struct MailExportedFcns)) + goto LBL_Error; + + pYAMNMailFcn->SynchroMessagesFcn = (YAMN_SYNCHROMIMEMSGSFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_SYNCHROMIMEMSGSID, 0); + pYAMNMailFcn->TranslateHeaderFcn = (YAMN_TRANSLATEHEADERFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_TRANSLATEHEADERID, 0); + pYAMNMailFcn->AppendQueueFcn = (YAMN_APPENDQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_APPENDQUEUEID, 0); + pYAMNMailFcn->DeleteMessagesToEndFcn = (YAMN_DELETEMIMEQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_DELETEMIMEQUEUEID, 0); + pYAMNMailFcn->DeleteMessageFromQueueFcn = (YAMN_DELETEMIMEMESSAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_DELETEMIMEMESSAGEID, 0); + pYAMNMailFcn->FindMessageByIDFcn = (YAMN_FINDMIMEMESSAGEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_FINDMIMEMESSAGEID, 0); + pYAMNMailFcn->CreateNewDeleteQueueFcn = (YAMN_CREATENEWDELETEQUEUEFCN)CallService(MS_YAMN_GETFCNPTR, (WPARAM)YAMN_CREATENEWDELETEQUEUEID, 0); + + //set static variable + if (CPOP3Account::AccountWriterSO == nullptr) + if (nullptr == (CPOP3Account::AccountWriterSO = new SCOUNTER)) + goto LBL_Error; + + //First, we register this plugin + //it is quite impossible this function returns zero (failure) as YAMN and internal plugin structre versions are the same + POP3ProtocolRegistration.Name = Translate("POP3 protocol (internal)"); + POP3ProtocolRegistration.Description = Translate(__DESCRIPTION); + if (nullptr == (POP3Plugin = (HYAMNPROTOPLUGIN)CallService(MS_YAMN_REGISTERPROTOPLUGIN, (WPARAM)&POP3ProtocolRegistration, (LPARAM)YAMN_PROTOREGISTRATIONVERSION))) + return 0; + + //Next we set our imported functions for YAMN + if (!SetProtocolPluginFcnImport(POP3Plugin, &POP3ProtocolFunctions, YAMN_PROTOIMPORTFCNVERSION, &POP3MailFunctions, YAMN_MAILIMPORTFCNVERSION)) + return 0; + + //Then, we read all mails for accounts. + //You must first register account, before using this function as YAMN must use CreatePOP3Account function to add new accounts + //But if CreatePOP3Account is not implemented (equals to NULL), YAMN creates account as YAMN's standard CAccount * + if (FileName) CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); //shoud not happen (only for secure) + FileName = (wchar_t *)CallService(MS_YAMN_GETFILENAME, (WPARAM)L"pop3", 0); + + switch (CallService(MS_YAMN_READACCOUNTS, (WPARAM)POP3Plugin, (LPARAM)FileName)) { + case EACC_FILEVERSION: + MessageBox(nullptr, TranslateT("Found new version of account book, not compatible with this version of YAMN."), TranslateT("YAMN (internal POP3) read error"), MB_OK); + CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); + FileName = nullptr; + return 0; + case EACC_FILECOMPATIBILITY: + MessageBox(nullptr, TranslateT("Error reading account file. Account file corrupted."), TranslateT("YAMN (internal POP3) read error"), MB_OK); + CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); + FileName = nullptr; + return 0; + case EACC_ALLOC: + MessageBox(nullptr, TranslateT("Memory allocation error while data reading"), TranslateT("YAMN (internal POP3) read error"), MB_OK); + CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); + FileName = nullptr; + return 0; + case EACC_SYSTEM: + if (ERROR_FILE_NOT_FOUND != GetLastError()) { + wchar_t temp[1024] = {0}; + mir_snwprintf(temp, L"%s\n%s", TranslateT("Reading file error. File already in use?"), FileName); + MessageBox(nullptr, temp, TranslateT("YAMN (internal POP3) read error"), MB_OK); + CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); + FileName = nullptr; + return 0; + } + break; + } + + for (CAccount *Finder = POP3Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { + Finder->hContact = NULL; + for (auto &hContact : Contacts(YAMN_DBMODULE)) { + DBVARIANT dbv; + if (!g_plugin.getString(hContact, "Id", &dbv)) { + if (mir_strcmp(dbv.pszVal, Finder->Name) == 0) { + Finder->hContact = hContact; + g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_ONLINE); + db_set_s(Finder->hContact, "CList", "StatusMsg", Translate("No new mail message")); + if ((Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) + Contact::Hide(Finder->hContact, false); + + if (!(Finder->Flags & YAMN_ACC_ENA) || !(Finder->NewMailN.Flags & YAMN_ACC_CONT)) + Contact::Hide(Finder->hContact); + } + db_free(&dbv); + } + } + + if (!Finder->hContact && (Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) { + // No account contact found, have to create one + Finder->hContact = db_add_contact(); + Proto_AddToContact(Finder->hContact, YAMN_DBMODULE); + g_plugin.setString(Finder->hContact, "Id", Finder->Name); + g_plugin.setString(Finder->hContact, "Nick", Finder->Name); + g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_OFFLINE); + } + } + + return 0; +} + +DWORD WINAPI UnLoadPOP3(void *) +{ + //pYAMNVar is only a pointr, no need delete or free + if (hNetLib) { + Netlib_CloseHandle(hNetLib); hNetLib = nullptr; + } + if (CPOP3Account::AccountWriterSO) { + delete CPOP3Account::AccountWriterSO; CPOP3Account::AccountWriterSO = nullptr; + } + if (pYAMNMailFcn) { + delete pYAMNMailFcn; pYAMNMailFcn = nullptr; + } + if (pYAMNFcn) { + delete pYAMNFcn; pYAMNFcn = nullptr; + } + if (FileName) { + CallService(MS_YAMN_DELETEFILENAME, (WPARAM)FileName, 0); FileName = nullptr; + } + + return 1; +} + +DWORD WINAPI WritePOP3Accounts() +{ + uint32_t ReturnValue = CallService(MS_YAMN_WRITEACCOUNTS, (WPARAM)POP3Plugin, (LPARAM)FileName); + if (ReturnValue == EACC_SYSTEM) { + wchar_t temp[1024] = {0}; + mir_snwprintf(temp, L"%s\n%s", TranslateT("Error while copying data to disk occurred. Is file in use?"), FileName); + MessageBox(nullptr, temp, TranslateT("POP3 plugin - write file error"), MB_OK); + } + + return ReturnValue; +} + +DWORD WINAPI WritePOP3Options(HANDLE File, CAccount *Which) +{ + DWORD WrittenBytes; + uint32_t Ver = POP3_FILEVERSION; + + if ((!WriteFile(File, (char *)&Ver, sizeof(uint32_t), &WrittenBytes, nullptr)) || + (!WriteFile(File, (char *)&((HPOP3ACCOUNT)Which)->CP, sizeof(uint16_t), &WrittenBytes, nullptr))) + return EACC_SYSTEM; + return 0; +} + +DWORD WINAPI ReadPOP3Options(CAccount *Which, char **Parser, char *End) +{ + uint32_t Ver; + #ifdef DEBUG_FILEREAD + wchar_t Debug[256]; + #endif + Ver = *(uint32_t *)(*Parser); + (*Parser) += sizeof(uint32_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + if (Ver != POP3_FILEVERSION) + return EACC_FILECOMPATIBILITY; + + ((HPOP3ACCOUNT)Which)->CP = *(uint16_t *)(*Parser); + (*Parser) += sizeof(uint16_t); + if (*Parser >= End) + return EACC_FILECOMPATIBILITY; + #ifdef DEBUG_FILEREAD + mir_snwprintf(Debug, L"CodePage: %d, remaining %d chars", ((HPOP3ACCOUNT)Which)->CP, End - *Parser); + MessageBox(NULL, Debug, L"debug", MB_OK); + #endif + return 0; +} + +HYAMNMAIL WINAPI CreatePOP3Mail(CAccount *Account, DWORD) +{ + HYAMNMAIL NewMail; + //First, we should check whether MAILDATA matches. + //But this is internal plugin, so YAMN's MAILDATA structure and our MAILDATA structure are + //the same, so we do not need to test version. Otherwise, if MAILDATA version does not match + //in your plugin, you should return NULL, like this: + // if (MailDataVersion != YAMN_MAILDATAVERSION) return NULL; + + //Now it is needed to construct our POP3 account and return its handle + if (nullptr == (NewMail = new YAMNMAIL)) + return nullptr; + + if (nullptr == (NewMail->MailData = new CMailData())) { + delete NewMail; + return nullptr; + } + NewMail->MailData->CP = ((HPOP3ACCOUNT)Account)->CP; + return (HYAMNMAIL)NewMail; +} + +static void SetContactStatus(CAccount *account, int status) +{ + if ((account->hContact) && (account->NewMailN.Flags & YAMN_ACC_CONT)) + g_plugin.setWord(account->hContact, "Status", status); +} + +static void PostErrorProc(HPOP3ACCOUNT ActualAccount, void *ParamToBadConnection, uint32_t POP3PluginParam, BOOL UseSSL) +{ + char *DataRX; + + //We create new structure, that we pass to bad connection dialog procedure. This procedure next calls YAMN imported fuction + //from POP3 protocol to determine the description of error. We can describe error from our error code structure, because later, + //when YAMN calls our function, it passes us our error code. This is pointer to structure for POP3 protocol in fact. + PPOP3_ERRORCODE ErrorCode; + + //We store status before we do Quit(), because quit can destroy our errorcode status + if (nullptr != (ErrorCode = new POP3_ERRORCODE)) { + ErrorCode->SSL = UseSSL; + ErrorCode->AppError = ActualAccount->SystemError; + ErrorCode->POP3Error = ActualAccount->Client.POP3Error; + ErrorCode->NetError = ActualAccount->Client.NetClient->NetworkError; + ErrorCode->SystemError = ActualAccount->Client.NetClient->SystemError; + } + + if (POP3PluginParam == (uint32_t)NULL) //if it was normal YAMN call (force check or so on) + { + try { + DataRX = ActualAccount->Client.Quit(); + if (DataRX != nullptr) + free(DataRX); + } + catch (...) { + } + //We always close connection if error occured + try { + ActualAccount->Client.NetClient->Disconnect(); + } + catch (...) { + } + + SetAccountStatus(ActualAccount, TranslateT("Disconnected")); + + //If we cannot allocate memory, do nothing + if (ErrorCode == nullptr) { + SetEvent(ActualAccount->UseInternetFree); + return; + } + } + else // else it was called from POP3 plugin, probably error when deleting old mail (POP3 synchro calls POP3 delete) + if (ErrorCode == nullptr) + return; + + if ((ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) || (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) || (ActualAccount->BadConnectN.Flags & YAMN_ACC_POP)) + RunBadConnection(ActualAccount, (UINT_PTR)ErrorCode, ParamToBadConnection); + + if (POP3PluginParam == (uint32_t)NULL) //if it was normal YAMN call + SetEvent(ActualAccount->UseInternetFree); +} + +//Checks POP3 account and synchronizes it +DWORD WINAPI SynchroPOP3(struct CheckParam *WhichTemp) +{ + HPOP3ACCOUNT ActualAccount; + CPop3Client *MyClient; + HYAMNMAIL NewMails = nullptr, MsgQueuePtr = nullptr; + char *DataRX = nullptr, *Temp; + int mboxsize, msgs, i; + SYSTEMTIME now; + LPVOID YAMNParam; + uint32_t CheckFlags; + BOOL UsingInternet = FALSE; + struct + { + char *ServerName; + uint32_t ServerPort; + char *ServerLogin; + char *ServerPasswd; + uint32_t Flags; + uint32_t NFlags; + uint32_t NNFlags; + } ActualCopied; + + //First, we should compare our version of CheckParam structure, but here it is not needed, because YAMN and internal plugin + //have the same version. But your plugin should do that in this way: + // if (((struct CheckParam *)WhichTemp)->Ver != YAMN_CHECKVERSION) + // { + // SetEvent(((struct CheckParam *)WhichTemp)->ThreadRunningEV); //don't forget to unblock YAMN + // return (uint32_t)-1; //ok, but we should return value. + // //When our plugin returns e.g. 0xFFFFFFFF (=-1, this is only our plugin value, YAMN does nothing with return value, + // //but only tests if it is nonzero. If yes, it calls GetErrorStringFcn. We know problem occured in YAMN incompatibility + // //and then we can in our GetErrorStringFcn e.g. return string "Uncompatible version of YAMN". + // } + + ActualAccount = (HPOP3ACCOUNT)WhichTemp->AccountParam; //copy address of structure from calling thread to stack of this thread + YAMNParam = WhichTemp->BrowserParam; + CheckFlags = WhichTemp->Flags; + + SCInc(ActualAccount->UsingThreads); + //Unblock YAMN, signal that we have copied all parameters from YAMN thread stack + if (INVALID_HANDLE_VALUE != WhichTemp->ThreadRunningEV) + SetEvent(WhichTemp->ThreadRunningEV); + + if (WAIT_OBJECT_0 != WaitToRead(ActualAccount)) { + SCDec(ActualAccount->UsingThreads); + return 0; + } + + MyClient = &ActualAccount->Client; + //Now, copy all needed information about account to local variables, so ActualAccount is not blocked in read mode during all connection process, which can last for several minutes. + ActualCopied.ServerName = _strdup(ActualAccount->Server->Name); + ActualCopied.ServerPort = ActualAccount->Server->Port; + ActualCopied.Flags = ActualAccount->Flags; + ActualCopied.ServerLogin = _strdup(ActualAccount->Server->Login); + ActualCopied.ServerPasswd = _strdup(ActualAccount->Server->Passwd); + ActualCopied.NFlags = ActualAccount->NewMailN.Flags; + ActualCopied.NNFlags = ActualAccount->NoNewMailN.Flags; + + ReadDone(ActualAccount); + SCInc(ActualAccount->InternetQueries); //increment counter, that there is one more thread waiting for connection + + WaitForSingleObject(ActualAccount->UseInternetFree, INFINITE); //wait until we can use connection + SCDec(ActualAccount->InternetQueries); + + //OK, we enter the "use internet" section. But after we start communication, we can test if we did not enter the "use internet" section only for the reason, + //that previous thread release the internet section because this account has stop signal (we stop account and there are 2 threads: one communicating, + //the second one waiting for network access- the first one ends because we want to stop account, this one is released, but should be stopped as well). + if (!ActualAccount->AbleToWork) { + SetEvent(ActualAccount->UseInternetFree); + SCDec(ActualAccount->UsingThreads); + return 0; + } + UsingInternet = TRUE; + + GetLocalTime(&now); + ActualAccount->SystemError = 0; //now we can use internet for this socket. First, clear errorcode. + try { + SetContactStatus(ActualAccount, ID_STATUS_OCCUPIED); + + // if we are already connected, we have open session (another thread left us open session), so we don't need to login + // note that connected state without logging cannot occur, because if we close session, we always close socket too (we must close socket is the right word :)) + if ((MyClient->NetClient == nullptr) || !MyClient->NetClient->Connected()) { + SetAccountStatus(ActualAccount, TranslateT("Connecting to server")); + + DataRX = MyClient->Connect(ActualCopied.ServerName, ActualCopied.ServerPort, ActualCopied.Flags & YAMN_ACC_SSL23, ActualCopied.Flags & YAMN_ACC_NOTLS); + char *timestamp = nullptr; + + if (DataRX != nullptr) { + if (ActualCopied.Flags & YAMN_ACC_APOP) { + char *lpos = strchr(DataRX, '<'); + char *rpos = strchr(DataRX, '>'); + if (lpos && rpos && rpos > lpos) { + int sz = (int)(rpos - lpos + 2); + timestamp = new char[sz]; + memcpy(timestamp, lpos, sz - 1); + timestamp[sz - 1] = '\0'; + } + } + free(DataRX); + DataRX = nullptr; + } + + SetAccountStatus(ActualAccount, TranslateT("Entering POP3 account")); + + if (ActualCopied.Flags & YAMN_ACC_APOP) { + DataRX = MyClient->APOP(ActualCopied.ServerLogin, ActualCopied.ServerPasswd, timestamp); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + delete[] timestamp; + } + else { + DataRX = MyClient->User(ActualCopied.ServerLogin); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + DataRX = MyClient->Pass(ActualCopied.ServerPasswd); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + } + } + SetAccountStatus(ActualAccount, TranslateT("Searching for new mail message")); + + DataRX = MyClient->Stat(); + + ExtractStat(DataRX, &mboxsize, &msgs); + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + for (i = 0; i < msgs; i++) { + if (!i) + MsgQueuePtr = NewMails = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); + else { + MsgQueuePtr->Next = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); + MsgQueuePtr = MsgQueuePtr->Next; + } + if (MsgQueuePtr == nullptr) { + ActualAccount->SystemError = EPOP3_QUEUEALLOC; + throw (uint32_t)ActualAccount->SystemError; + } + } + + if (msgs) { + DataRX = MyClient->List(); + ExtractList(DataRX, MyClient->NetClient->Rcv, NewMails); + if (DataRX != nullptr) + free(DataRX); + + DataRX = MyClient->Uidl(); + ExtractUIDL(DataRX, MyClient->NetClient->Rcv, NewMails); + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + } + + if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) + throw (uint32_t)(ActualAccount->SystemError = EACC_STOPPED); + + ActualAccount->LastChecked = now; + for (MsgQueuePtr = (HYAMNMAIL)ActualAccount->Mails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next) { + if (MsgQueuePtr->Flags & YAMN_MSG_BODYREQUESTED) { + HYAMNMAIL NewMsgsPtr = nullptr; + for (NewMsgsPtr = (HYAMNMAIL)NewMails; NewMsgsPtr != nullptr; NewMsgsPtr = NewMsgsPtr->Next) { + if (!mir_strcmp(MsgQueuePtr->ID, NewMsgsPtr->ID)) { + wchar_t accstatus[512]; + mir_snwprintf(accstatus, TranslateT("Reading body %s"), NewMsgsPtr->ID); + SetAccountStatus(ActualAccount, accstatus); + DataRX = MyClient->Top(MsgQueuePtr->Number, 100); + if (DataRX != nullptr) { + Temp = DataRX; + while ((Temp < DataRX + MyClient->NetClient->Rcv) && (WS(Temp) || ENDLINE(Temp))) Temp++; + + if (OKLINE(DataRX)) + for (Temp = DataRX; (Temp < DataRX + MyClient->NetClient->Rcv) && (!ENDLINE(Temp)); Temp++); + while ((Temp < DataRX + MyClient->NetClient->Rcv) && ENDLINE(Temp)) Temp++; + } + else + continue; + //delete all the headers of the old mail MsgQueuePtr->MailData->TranslatedHeader + struct CMimeItem *TH = MsgQueuePtr->MailData->TranslatedHeader; + if (TH) for (; MsgQueuePtr->MailData->TranslatedHeader != nullptr;) { + TH = TH->Next; + if (MsgQueuePtr->MailData->TranslatedHeader->name != nullptr) + delete[] MsgQueuePtr->MailData->TranslatedHeader->name; + if (MsgQueuePtr->MailData->TranslatedHeader->value != nullptr) + delete[] MsgQueuePtr->MailData->TranslatedHeader->value; + delete MsgQueuePtr->MailData->TranslatedHeader; + MsgQueuePtr->MailData->TranslatedHeader = TH; + } + + TranslateHeader(Temp, MyClient->NetClient->Rcv - (Temp - DataRX), &MsgQueuePtr->MailData->TranslatedHeader); + + MsgQueuePtr->Flags |= YAMN_MSG_BODYRECEIVED; + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + break; + } + } + } + } + + SynchroMessages(ActualAccount, (HYAMNMAIL *)&ActualAccount->Mails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); //we get only new mails on server! + + MsgsWriteDone(ActualAccount); + for (MsgQueuePtr = (HYAMNMAIL)ActualAccount->Mails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next) { + if ((MsgQueuePtr->Flags & YAMN_MSG_BODYREQUESTED) && (MsgQueuePtr->Flags & YAMN_MSG_BODYRECEIVED)) { + MsgQueuePtr->Flags &= ~YAMN_MSG_BODYREQUESTED; + if (MsgQueuePtr->MsgWindow) + SendMessage(MsgQueuePtr->MsgWindow, WM_YAMN_CHANGECONTENT, 0, 0); + } + } + + for (msgs = 0, MsgQueuePtr = NewMails; MsgQueuePtr != nullptr; MsgQueuePtr = MsgQueuePtr->Next, msgs++); //get number of new mails + + try { + wchar_t accstatus[512]; + + for (i = 0, MsgQueuePtr = NewMails; MsgQueuePtr != nullptr; i++) { + BOOL autoretr = (ActualAccount->Flags & YAMN_ACC_BODY) != 0; + DataRX = MyClient->Top(MsgQueuePtr->Number, autoretr ? 100 : 0); + mir_snwprintf(accstatus, TranslateT("Reading new mail messages (%d%% done)"), 100 * i / msgs); + SetAccountStatus(ActualAccount, accstatus); + + if (DataRX != nullptr) { + Temp = DataRX; + while ((Temp < DataRX + MyClient->NetClient->Rcv) && (WS(Temp) || ENDLINE(Temp))) Temp++; + + if (OKLINE(DataRX)) + for (Temp = DataRX; (Temp < DataRX + MyClient->NetClient->Rcv) && (!ENDLINE(Temp)); Temp++); + while ((Temp < DataRX + MyClient->NetClient->Rcv) && ENDLINE(Temp)) Temp++; + } + else + continue; + + TranslateHeader(Temp, MyClient->NetClient->Rcv - (Temp - DataRX), &MsgQueuePtr->MailData->TranslatedHeader); + + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + MsgQueuePtr->Flags |= YAMN_MSG_NORMALNEW; + if (autoretr) MsgQueuePtr->Flags |= YAMN_MSG_BODYRECEIVED; + + //We are going to filter mail. Warning!- we must not be in read access neither write access to mails when calling this service + //This is done, because the "NewMails" queue is not synchronised. It is because it is new queue. Only this thread uses new queue yet, it is not + //connected to account mail queue. + // CallService(MS_YAMN_FILTERMAIL,(WPARAM)ActualAccount,(LPARAM)MsgQueuePtr); + FilterMailSvc((WPARAM)ActualAccount, (LPARAM)MsgQueuePtr); + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + + //MsgQueuePtr->MailData->Body=MyClient->Retr(MsgQueuePtr->Number); + + MsgQueuePtr = MsgQueuePtr->Next; + + } + + if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) + throw (uint32_t)ActualAccount->SystemError == EACC_STOPPED; + + if (ActualAccount->Mails == nullptr) + ActualAccount->Mails = NewMails; + else { + ActualAccount->LastMail = ActualAccount->LastChecked; + AppendQueue((HYAMNMAIL)ActualAccount->Mails, NewMails); + } + + MsgsWriteDone(ActualAccount); + + // we are going to delete mails having SPAM flag level3 and 4 (see m_mails.h) set + { + struct DeleteParam ParamToDeleteMails = {YAMN_DELETEVERSION, INVALID_HANDLE_VALUE, ActualAccount, YAMNParam, (void *)POP3_DELETEFROMCHECK}; + + // Delete mails from server. Here we should not be in write access for account's mails + DeleteMailsPOP3(&ParamToDeleteMails); + } + + // if there is no waiting thread for internet connection close it + // else leave connection open + if (0 == SCGetNumber(ActualAccount->InternetQueries)) { + DataRX = MyClient->Quit(); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + MyClient->NetClient->Disconnect(); + + SetAccountStatus(ActualAccount, TranslateT("Disconnected")); + } + + UsingInternet = FALSE; + SetEvent(ActualAccount->UseInternetFree); + + ActualAccount->LastSChecked = ActualAccount->LastChecked; + ActualAccount->LastSynchronised = ActualAccount->LastChecked; + } + catch (...) { + throw; //go to the main exception handling + } + + { + YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualCopied.NFlags, ActualCopied.NNFlags, YAMNParam}; + + if (CheckFlags & YAMN_FORCECHECK) + Param.nnflags |= YAMN_ACC_POP; //if force check, show popup anyway and if mailbrowser was opened, do not close + Param.nnflags |= YAMN_ACC_MSGP; //do not close browser if already open + CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); + } + SetContactStatus(ActualAccount, ActualAccount->isCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); + } + #ifdef DEBUG_COMM + catch (uint32_t ErrorCode) + #else + catch (uint32_t) + #endif + { + if (ActualAccount->Client.POP3Error == EPOP3_STOPPED) + ActualAccount->SystemError = EACC_STOPPED; + #ifdef DEBUG_COMM + DebugLog(CommFile, "ERROR: %x\n", ErrorCode); + #endif + if (WAIT_OBJECT_0 == MsgsWaitToWrite(ActualAccount)) { + ActualAccount->LastChecked = now; + MsgsWriteDone(ActualAccount); + } + + DeleteMIMEQueue(ActualAccount, NewMails); + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + switch (ActualAccount->SystemError) { + case EACC_QUEUEALLOC: + case EACC_STOPPED: + ActualAccount->Client.NetClient->Disconnect(); + break; + default: + PostErrorProc(ActualAccount, YAMNParam, (uint32_t)NULL, MyClient->SSL); //it closes internet connection too + } + + if (UsingInternet) //if our thread still uses internet + SetEvent(ActualAccount->UseInternetFree); + + SetContactStatus(ActualAccount, ID_STATUS_NA); + } + free(ActualCopied.ServerName); + free(ActualCopied.ServerLogin); + free(ActualCopied.ServerPasswd); + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + + // WriteAccounts(); + SCDec(ActualAccount->UsingThreads); + return 0; +} + +void __cdecl DeleteMailsPOP3(void *param) +{ + DeleteParam *WhichTemp = (DeleteParam *)param; + + CPop3Client *MyClient; + HYAMNMAIL DeleteMails, NewMails = nullptr, MsgQueuePtr = nullptr; + char *DataRX = nullptr; + int mboxsize = 0, msgs = 0, i; + BOOL UsingInternet = FALSE; + struct + { + char *ServerName; + uint32_t ServerPort; + char *ServerLogin; + char *ServerPasswd; + uint32_t Flags; + uint32_t NFlags; + uint32_t NNFlags; + } ActualCopied; + + //First, we should compare our version of DeleteParam structure, but here it is not needed, because YAMN and internal plugin + //have the same version. But your plugin should do that in this way: + // if (((struct DeleteParam *)WhichTemp)->Ver != YAMN_DELETEVERSION) + // { + // SetEvent(((struct DeleteParam *)WhichTemp)->ThreadRunningEV); //don't forget to unblock YAMN + // return (uint32_t)-1; //ok, but we should return value. + // //When our plugin returns e.g. 0xFFFFFFFF (this is only our plugin value, YAMN does nothing with return value, + // //but only tests if it is nonzero. If yes, it calls GetErrorStringFcn), we know problem occured in YAMN incompatibility + // //and then we can in our GetErrorStringFcn e.g. return string "Uncompatible version of YAMN". + // } + + HPOP3ACCOUNT ActualAccount = (HPOP3ACCOUNT)WhichTemp->AccountParam; //copy address of structure from calling thread to stack of this thread + LPVOID YAMNParam = WhichTemp->BrowserParam; + UINT_PTR POP3PluginParam = (UINT_PTR)((struct DeleteParam *)WhichTemp)->CustomParam; + + SCInc(ActualAccount->UsingThreads); + if (INVALID_HANDLE_VALUE != WhichTemp->ThreadRunningEV) + SetEvent(WhichTemp->ThreadRunningEV); + + if (WAIT_OBJECT_0 != WaitToRead(ActualAccount)) { + SCDec(ActualAccount->UsingThreads); + return; + } + + if (nullptr == (DeleteMails = (HYAMNMAIL)CreateNewDeleteQueue((HYAMNMAIL)ActualAccount->Mails))) //if there's no mail for deleting, return + { + if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not wait for free internet when calling from SynchroPOP3. It is because UseInternetFree is blocked + { + YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, YAMN_ACC_MSGP, YAMN_ACC_MSGP, YAMNParam}; //Just update the window + CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); + } + + SCDec(ActualAccount->UsingThreads); + return; + } + + MyClient = &(ActualAccount->Client); + + //Now, copy all needed information about account to local variables, so ActualAccount is not blocked in read mode during all connection process, which can last for several minutes. + ActualCopied.ServerName = _strdup(ActualAccount->Server->Name); + ActualCopied.ServerPort = ActualAccount->Server->Port; + ActualCopied.Flags = ActualAccount->Flags; + ActualCopied.ServerLogin = _strdup(ActualAccount->Server->Login); + ActualCopied.ServerPasswd = _strdup(ActualAccount->Server->Passwd); + ActualCopied.NFlags = ActualAccount->NewMailN.Flags; + ActualCopied.NNFlags = ActualAccount->NoNewMailN.Flags; + + ReadDone(ActualAccount); + + SCInc(ActualAccount->InternetQueries); //This is POP3-internal SCOUNTER, we set another thread wait for this account to be connected to inet + if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not wait for free internet when calling from SynchroPOP3. It is because UseInternetFree is blocked + WaitForSingleObject(ActualAccount->UseInternetFree, INFINITE); + + SCDec(ActualAccount->InternetQueries); + UsingInternet = TRUE; + + try { + SetContactStatus(ActualAccount, ID_STATUS_OCCUPIED); + #ifdef DEBUG_COMM + DebugLog(CommFile, "<--------Communication-------->\n"); + #endif + if ((MyClient->NetClient == nullptr) || !MyClient->NetClient->Connected()) { + SetAccountStatus(ActualAccount, TranslateT("Connecting to server")); + + DataRX = MyClient->Connect(ActualCopied.ServerName, ActualCopied.ServerPort, ActualCopied.Flags & YAMN_ACC_SSL23, ActualCopied.Flags & YAMN_ACC_NOTLS); + + char *timestamp = nullptr; + if (DataRX != nullptr) { + if (ActualAccount->Flags & YAMN_ACC_APOP) { + char *lpos = strchr(DataRX, '<'); + char *rpos = strchr(DataRX, '>'); + if (lpos && rpos && rpos > lpos) { + int sz = (int)(rpos - lpos + 2); + timestamp = new char[sz]; + memcpy(timestamp, lpos, sz - 1); + timestamp[sz - 1] = '\0'; + } + } + free(DataRX); + DataRX = nullptr; + } + SetAccountStatus(ActualAccount, TranslateT("Entering POP3 account")); + + if (ActualAccount->Flags & YAMN_ACC_APOP) { + DataRX = MyClient->APOP(ActualCopied.ServerLogin, ActualCopied.ServerPasswd, timestamp); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + delete[] timestamp; + } + else { + DataRX = MyClient->User(ActualCopied.ServerLogin); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + DataRX = MyClient->Pass(ActualCopied.ServerPasswd); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + } + } + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "<--------Deleting requested mails-------->\n"); + #endif + if (POP3_DELETEFROMCHECK != POP3PluginParam) //We do not need to get mails on server as we have already it from check function + { + SetAccountStatus(ActualAccount, TranslateT("Deleting requested mails")); + + DataRX = MyClient->Stat(); + + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + ExtractStat(DataRX, &mboxsize, &msgs); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%d\n", mboxsize); + DebugLog(DecodeFile, "%d\n", msgs); + DebugLog(DecodeFile, "\n"); + #endif + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + for (i = 0; i < msgs; i++) { + if (!i) + MsgQueuePtr = NewMails = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); + else { + MsgQueuePtr->Next = (HYAMNMAIL)CallService(MS_YAMN_CREATEACCOUNTMAIL, (WPARAM)ActualAccount, (LPARAM)YAMN_MAILVERSION); + MsgQueuePtr = MsgQueuePtr->Next; + } + if (MsgQueuePtr == nullptr) { + ActualAccount->SystemError = EPOP3_QUEUEALLOC; + throw (uint32_t)ActualAccount->SystemError; + } + } + + if (msgs) { + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + DataRX = MyClient->Uidl(); + ExtractUIDL(DataRX, MyClient->NetClient->Rcv, NewMails); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + // we get "new mails" on server (NewMails will contain all mails on server not found in DeleteMails) + // but also in DeleteMails we get only those, which are still on server with their responsable numbers + SynchroMessages(ActualAccount, (HYAMNMAIL *)&DeleteMails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); + } + } + else SetAccountStatus(ActualAccount, TranslateT("Deleting spam")); + + if (WAIT_OBJECT_0 != MsgsWaitToWrite(ActualAccount)) + throw (uint32_t)EACC_STOPPED; + + if (msgs || POP3_DELETEFROMCHECK == POP3PluginParam) { + try { + HYAMNMAIL Temp; + + for (i = 0, MsgQueuePtr = DeleteMails; MsgQueuePtr != nullptr; i++) { + if (!(MsgQueuePtr->Flags & YAMN_MSG_VIRTUAL)) //of course we can only delete real mails, not virtual + { + DataRX = MyClient->Dele(MsgQueuePtr->Number); + Temp = MsgQueuePtr->Next; + if (POP3_FOK == MyClient->AckFlag) //if server answers that mail was deleted + { + DeleteMIMEMessage((HYAMNMAIL *)&DeleteMails, MsgQueuePtr); + HYAMNMAIL DeletedMail = FindMIMEMessageByID((HYAMNMAIL)ActualAccount->Mails, MsgQueuePtr->ID); + if ((MsgQueuePtr->Flags & YAMN_MSG_MEMDELETE)) //if mail should be deleted from memory (or disk) + { + DeleteMIMEMessage((HYAMNMAIL *)&ActualAccount->Mails, DeletedMail); //remove from queue + CallService(MS_YAMN_DELETEACCOUNTMAIL, (WPARAM)POP3Plugin, (LPARAM)DeletedMail); + } + else //else mark it only as "deleted mail" + { + DeletedMail->Flags |= (YAMN_MSG_VIRTUAL | YAMN_MSG_DELETED); + DeletedMail->Flags &= ~(YAMN_MSG_NEW | YAMN_MSG_USERDELETE | YAMN_MSG_AUTODELETE); //clear "new mail" + } + delete MsgQueuePtr->MailData; + delete[] MsgQueuePtr->ID; + delete MsgQueuePtr; + } + MsgQueuePtr = Temp; + + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + } + else + MsgQueuePtr = MsgQueuePtr->Next; + } + } + catch (...) //if any exception in the code where we have write-access to account occured, don't forget to leave write-access + { + MsgsWriteDone(ActualAccount); + throw; //and go to the main exception handling + } + + if (NewMails != nullptr) + // in ActualAccount->Mails we have all mails stored before calling this function + // in NewMails we have all mails not found in DeleteMails (in other words: we performed new ID checking and we + // stored all mails found on server, then we deleted the ones we wanted to delete in this function + // and NewMails queue now contains actual state of mails on server). But we will not use NewMails as actual state, because NewMails does not contain header data (subject, from...) + // We perform deleting from ActualAccount->Mails: we remove from original queue (ActualAccount->Mails) all deleted mails + SynchroMessages(ActualAccount, (HYAMNMAIL *)&ActualAccount->Mails, nullptr, (HYAMNMAIL *)&NewMails, nullptr); + // Now ActualAccount->Mails contains all mails when calling this function except the ones, we wanted to delete (these are in DeleteMails) + // And in NewMails we have new mails (if any) + else if (POP3_DELETEFROMCHECK != POP3PluginParam) { + DeleteMIMEQueue(ActualAccount, (HYAMNMAIL)ActualAccount->Mails); + ActualAccount->Mails = nullptr; + } + } + else { + DeleteMIMEQueue(ActualAccount, (HYAMNMAIL)ActualAccount->Mails); + ActualAccount->Mails = nullptr; + } + + MsgsWriteDone(ActualAccount); + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + + // TODO: now, we have in NewMails new mails. If NewMails is not NULL, we found some new mails, so Checking for new mail should be performed + // now, we do not call CheckPOP3 + + // if there is no waiting thread for internet connection close it + // else leave connection open + // if this functin was called from SynchroPOP3, then do not try to disconnect + if (POP3_DELETEFROMCHECK != POP3PluginParam) { + YAMN_MAILBROWSERPARAM Param = {(HANDLE)nullptr, ActualAccount, ActualCopied.NFlags, YAMN_ACC_MSGP, YAMNParam}; + + CallService(MS_YAMN_MAILBROWSER, (WPARAM)&Param, (LPARAM)YAMN_MAILBROWSERVERSION); + + if (0 == SCGetNumber(ActualAccount->InternetQueries)) { + DataRX = MyClient->Quit(); + if (DataRX != nullptr) + free(DataRX); + DataRX = nullptr; + MyClient->NetClient->Disconnect(); + + SetAccountStatus(ActualAccount, TranslateT("Disconnected")); + } + + UsingInternet = FALSE; + SetEvent(ActualAccount->UseInternetFree); + } + SetContactStatus(ActualAccount, ActualAccount->isCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); + } + #ifdef DEBUG_COMM + catch (uint32_t ErrorCode) + #else + catch (uint32_t) + #endif + { + if (ActualAccount->Client.POP3Error == EPOP3_STOPPED) + ActualAccount->SystemError = EACC_STOPPED; + #ifdef DEBUG_COMM + DebugLog(CommFile, "ERROR %x\n", ErrorCode); + #endif + if (DataRX != nullptr) + free(DataRX); + switch (ActualAccount->SystemError) { + case EACC_QUEUEALLOC: + case EACC_STOPPED: + ActualAccount->Client.NetClient->Disconnect(); + break; + default: + PostErrorProc(ActualAccount, YAMNParam, POP3PluginParam, MyClient->SSL); //it closes internet connection too + } + + if (UsingInternet && (POP3_DELETEFROMCHECK != POP3PluginParam)) //if our thread still uses internet and it is needed to release internet + SetEvent(ActualAccount->UseInternetFree); + } + + free(ActualCopied.ServerName); + free(ActualCopied.ServerLogin); + free(ActualCopied.ServerPasswd); + + DeleteMIMEQueue(ActualAccount, NewMails); + DeleteMIMEQueue(ActualAccount, DeleteMails); + + #ifdef DEBUG_COMM + DebugLog(CommFile, "\n"); + #endif + + // WriteAccounts(); + SCDec(ActualAccount->UsingThreads); + return; +} + +void ExtractStat(char *stream, int *mboxsize, int *mails) +{ + char *finder = stream; + while (WS(finder) || ENDLINE(finder)) finder++; + if (ACKLINE(finder)) { + while (!WS(finder)) finder++; + while (WS(finder)) finder++; + } + if (1 != sscanf(finder, "%d", mails)) + throw (uint32_t)EPOP3_STAT; + while (!WS(finder)) finder++; + while (WS(finder)) finder++; + if (1 != sscanf(finder, "%d", mboxsize)) + throw (uint32_t)EPOP3_STAT; +} +void ExtractMail(char *stream, int len, HYAMNMAIL queue) +{ + char *finder = stream; + char *finderend; + int msgnr, i; + HYAMNMAIL queueptr = queue; + + while (WS(finder) || ENDLINE(finder)) finder++; + while (!ACKLINE(finder)) finder++; + while (!ENDLINE(finder)) finder++; //now we at the end of first ack line + while (finder <= (stream + len)) { + while (ENDLINE(finder)) finder++; //go to the new line + if (DOTLINE(finder + 1)) //at the end of stream + break; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + while (WS(finder)) finder++; //jump whitespace + if (1 != sscanf(finder, "%d", &msgnr)) + throw (uint32_t)EPOP3_UIDL; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%d\n", msgnr); + #endif + // for (i=1,queueptr=queue;(queueptr->Next != NULL) && (iNext,i++); + // if (i != msgnr) + // throw (uint32_t)EPOP3_UIDL; + while (!WS(finder)) finder++; //jump characters + while (WS(finder)) finder++; //jump whitespace + finderend = finder + 1; + while (!WS(finderend) && !ENDLINE(finderend)) finderend++; + queueptr->ID = new char[finderend - finder + 1]; + for (i = 0; finder != finderend; finder++, i++) + queueptr->MailData->Body[i] = *finder; + queueptr->MailData->Body[i] = 0; //ends string + queueptr->Number = msgnr; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%s\n", queueptr->MailData->Body); + DebugLog(DecodeFile, "\n"); + #endif + queueptr = queueptr->Next; + while (!ENDLINE(finder)) finder++; + } +} + +void ExtractUIDL(char *stream, int len, HYAMNMAIL queue) +{ + char *finder = stream; + char *finderend; + int msgnr, i; + HYAMNMAIL queueptr = queue; + + while (WS(finder) || ENDLINE(finder)) finder++; + while (!ACKLINE(finder)) finder++; + while (!ENDLINE(finder)) finder++; //now we at the end of first ack line + while (finder <= (stream + len)) { + while (ENDLINE(finder)) finder++; //go to the new line + if (DOTLINE(finder + 1)) //at the end of stream + break; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n"); + #endif + while (WS(finder)) finder++; //jump whitespace + if (1 != sscanf(finder, "%d", &msgnr)) + throw (uint32_t)EPOP3_UIDL; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%d\n", msgnr); + #endif + // for (i=1,queueptr=queue;(queueptr->Next != NULL) && (iNext,i++); + // if (i != msgnr) + // throw (uint32_t)EPOP3_UIDL; + while (!WS(finder)) finder++; //jump characters + while (WS(finder)) finder++; //jump whitespace + finderend = finder + 1; + while (!WS(finderend) && !ENDLINE(finderend)) finderend++; + queueptr->ID = new char[finderend - finder + 1]; + for (i = 0; finder != finderend; finder++, i++) + queueptr->ID[i] = *finder; + queueptr->ID[i] = 0; //ends string + queueptr->Number = msgnr; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%s\n", queueptr->ID); + DebugLog(DecodeFile, "\n"); + #endif + queueptr = queueptr->Next; + while (!ENDLINE(finder)) finder++; + } +} + +void ExtractList(char *stream, int len, HYAMNMAIL queue) +{ + char *finder = stream; + char *finderend; + int msgnr, i; + HYAMNMAIL queueptr; + + while (WS(finder) || ENDLINE(finder)) finder++; + while (!ACKLINE(finder)) finder++; + while (!ENDLINE(finder)) finder++; //now we at the end of first ack line + while (finder <= (stream + len)) { + while (ENDLINE(finder)) finder++; //go to the new line + if (DOTLINE(finder + 1)) //at the end of stream + break; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "\n", NULL, 0); + #endif + while (WS(finder)) finder++; //jump whitespace + if (1 != sscanf(finder, "%d", &msgnr)) //message nr. + throw (uint32_t)EPOP3_LIST; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%d\n", msgnr); + #endif + + for (i = 1, queueptr = queue; (queueptr->Next != nullptr) && (i < msgnr); queueptr = queueptr->Next, i++); + if (i != msgnr) + throw (uint32_t)EPOP3_LIST; + while (!WS(finder)) finder++; //jump characters + while (WS(finder)) finder++; //jump whitespace + finderend = finder + 1; + if (1 != sscanf(finder, "%u", &queueptr->MailData->Size)) + throw (uint32_t)EPOP3_LIST; + #ifdef DEBUG_DECODE + DebugLog(DecodeFile, "%d\n", queueptr->MailData->Size); + #endif + while (!ENDLINE(finder)) finder++; + } +} + +wchar_t *WINAPI GetErrorString(DWORD Code) +{ + static wchar_t *POP3Errors[] = + { + LPGENW("Memory allocation error."), //memory allocation + LPGENW("Account is about to be stopped."), //stop account + LPGENW("Cannot connect to POP3 server."), + LPGENW("Cannot allocate memory for received data."), + LPGENW("Cannot login to POP3 server."), + LPGENW("Bad user or password."), + LPGENW("Server does not support APOP authorization."), + LPGENW("Error while executing POP3 command."), + LPGENW("Error while executing POP3 command."), + LPGENW("Error while executing POP3 command."), + }; + + static wchar_t *NetlibErrors[] = + { + LPGENW("Cannot connect to server with NetLib."), + LPGENW("Cannot send data."), + LPGENW("Cannot receive data."), + LPGENW("Cannot allocate memory for received data."), + }; + + static wchar_t *SSLErrors[] = + { + LPGENW("OpenSSL not loaded."), + LPGENW("Windows socket 2.0 init failed."), + LPGENW("DNS lookup error."), + LPGENW("Error while creating base socket."), + LPGENW("Error connecting to server with socket."), + LPGENW("Error while creating SSL structure."), + LPGENW("Error connecting socket with SSL."), + LPGENW("Server rejected connection with SSL."), + LPGENW("Cannot write SSL data."), + LPGENW("Cannot read SSL data."), + LPGENW("Cannot allocate memory for received data."), + }; + + wchar_t *ErrorString = new wchar_t[ERRORSTR_MAXLEN]; + POP3_ERRORCODE *ErrorCode = (POP3_ERRORCODE *)(UINT_PTR)Code; + + mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, TranslateT("Error %d-%d-%d-%d:"), ErrorCode->AppError, ErrorCode->POP3Error, ErrorCode->NetError, ErrorCode->SystemError); + if (ErrorCode->POP3Error) + mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(POP3Errors[ErrorCode->POP3Error - 1])); + if (ErrorCode->NetError) { + if (ErrorCode->SSL) + mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(SSLErrors[ErrorCode->NetError - 1])); + else + mir_snwprintf(ErrorString, ERRORSTR_MAXLEN, L"%s\n%s", ErrorString, TranslateW(NetlibErrors[ErrorCode->NetError - 4])); + } + + return ErrorString; +} + +void WINAPI DeleteErrorString(LPVOID String) +{ + delete (char *)String; +} diff --git a/protocols/YAMN/src/proto/pop3/pop3opt.cpp b/protocols/YAMN/src/proto/pop3/pop3opt.cpp index 80764d85bb..a1dfa14199 100644 --- a/protocols/YAMN/src/proto/pop3/pop3opt.cpp +++ b/protocols/YAMN/src/proto/pop3/pop3opt.cpp @@ -1,1137 +1,1137 @@ -/* - * This code implements POP3 options window handling - * - * (c) majvan 2002-2003 -*/ - -#include "../../stdafx.h" - -//-------------------------------------------------------------------------------------------------- - -static char DlgInput[MAX_PATH]; - -static BOOL DlgSetItemText(HWND hDlg, WPARAM wParam, const char *str) -{ - if (str == nullptr) - SetDlgItemTextA(hDlg, wParam, ""); - else - SetDlgItemTextA(hDlg, wParam, str); - return TRUE; -} - -static BOOL DlgSetItemTextW(HWND hDlg, WPARAM wParam, const wchar_t *str) -{ - if (str == nullptr) - SetDlgItemTextW(hDlg, wParam, L""); - else - SetDlgItemTextW(hDlg, wParam, str); - return TRUE; -} - -struct CBaseOptionsDlg : public CDlgBase -{ - CBaseOptionsDlg(int iDlgId) : - CDlgBase(g_plugin, iDlgId) - {} - - void DlgShowAccount(HPOP3ACCOUNT pAccount) - { - int i; - - if (pAccount) { - // we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread - WaitToRead(pAccount); - - DlgSetItemText(m_hwnd, IDC_EDITSERVER, pAccount->Server->Name); - DlgSetItemText(m_hwnd, IDC_EDITNAME, pAccount->Name); - DlgSetItemText(m_hwnd, IDC_EDITLOGIN, pAccount->Server->Login); - DlgSetItemText(m_hwnd, IDC_EDITPASS, pAccount->Server->Passwd); - DlgSetItemTextW(m_hwnd, IDC_EDITAPP, pAccount->NewMailN.App); - DlgSetItemTextW(m_hwnd, IDC_EDITAPPPARAM, pAccount->NewMailN.AppParam); - SetDlgItemInt(m_hwnd, IDC_EDITPORT, pAccount->Server->Port, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, pAccount->Interval / 60, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITPOPS, pAccount->NewMailN.PopupTime, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, pAccount->NoNewMailN.PopupTime, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, pAccount->BadConnectN.PopupTime, FALSE); - for (i = 0; i <= CPLENSUPP; i++) - if ((i < CPLENSUPP) && (CodePageNamesSupp[i].CP == pAccount->CP)) { - SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)i, 0); - break; - } - - if (i == CPLENSUPP) - SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)CPDEFINDEX, 0); - - CheckDlgButton(m_hwnd, IDC_CHECK, pAccount->Flags & YAMN_ACC_ENA ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSND, pAccount->NewMailN.Flags & YAMN_ACC_SND ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKMSG, pAccount->NewMailN.Flags & YAMN_ACC_MSG ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKICO, pAccount->NewMailN.Flags & YAMN_ACC_ICO ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKPOP, pAccount->NewMailN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCOL, pAccount->NewMailN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKAPP, pAccount->NewMailN.Flags & YAMN_ACC_APP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKKBN, pAccount->NewMailN.Flags & YAMN_ACC_KBN ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKNPOP, pAccount->NoNewMailN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKNCOL, pAccount->NoNewMailN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKNMSGP, pAccount->NoNewMailN.Flags & YAMN_ACC_MSGP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFSND, pAccount->BadConnectN.Flags & YAMN_ACC_SND ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFMSG, pAccount->BadConnectN.Flags & YAMN_ACC_MSG ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFICO, pAccount->BadConnectN.Flags & YAMN_ACC_ICO ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFPOP, pAccount->BadConnectN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFCOL, pAccount->BadConnectN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOPN, pAccount->Flags & YAMN_ACC_POPN ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOP1, pAccount->Flags & YAMN_ACC_POPN ? BST_UNCHECKED : BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSSL, pAccount->Flags & YAMN_ACC_SSL23 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKNOTLS, pAccount->Flags & YAMN_ACC_NOTLS ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKAPOP, pAccount->Flags & YAMN_ACC_APOP ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_AUTOBODY, pAccount->Flags & YAMN_ACC_BODY ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSTART, pAccount->StatusFlags & YAMN_ACC_STARTS ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFORCE, pAccount->StatusFlags & YAMN_ACC_FORCE ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCONTACT, pAccount->NewMailN.Flags & YAMN_ACC_CONT ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCONTACTNICK, pAccount->NewMailN.Flags & YAMN_ACC_CONTNICK ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCONTACTNOEVENT, pAccount->NewMailN.Flags & YAMN_ACC_CONTNOEVENT ? BST_CHECKED : BST_UNCHECKED); - - wchar_t accstatus[256]; - GetAccountStatus(pAccount, accstatus); - SetDlgItemText(m_hwnd, IDC_STSTATUS, accstatus); - ReadDone(pAccount); - } - else { - DlgSetItemText(m_hwnd, IDC_EDITSERVER, nullptr); - DlgSetItemText(m_hwnd, IDC_EDITNAME, nullptr); - DlgSetItemText(m_hwnd, IDC_EDITLOGIN, nullptr); - DlgSetItemText(m_hwnd, IDC_EDITPASS, nullptr); - DlgSetItemText(m_hwnd, IDC_EDITAPP, nullptr); - DlgSetItemText(m_hwnd, IDC_EDITAPPPARAM, nullptr); - DlgSetItemText(m_hwnd, IDC_STTIMELEFT, nullptr); - SetDlgItemInt(m_hwnd, IDC_EDITPORT, 110, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, 30, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITPOPS, 0, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, 0, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, 0, FALSE); - SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)CPDEFINDEX, 0); - CheckDlgButton(m_hwnd, IDC_CHECK, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSND, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKMSG, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKICO, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKPOP, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCOL, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKAPP, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFSND, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFMSG, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFICO, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFPOP, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFCOL, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSTART, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKFORCE, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOPN, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOP1, BST_CHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKSSL, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKNOTLS, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKAPOP, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_AUTOBODY, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CHECKCONTACT, BST_CHECKED); - - SetDlgItemText(m_hwnd, IDC_STSTATUS, TranslateT("No account selected")); - } - } -}; - -//======================================================================================= -// General options dialog - -struct CGeneralOptDlg : public CBaseOptionsDlg -{ - CGeneralOptDlg() : - CBaseOptionsDlg(IDD_YAMNOPT) - {} - - bool OnInitDialog() override - { - CheckDlgButton(m_hwnd, IDC_CHECKTTB, g_plugin.getByte(YAMN_TTBFCHECK, 1) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_LONGDATE, (optDateTime & SHOWDATELONG) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_SMARTDATE, (optDateTime & SHOWDATENOTODAY) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_NOSECONDS, (optDateTime & SHOWDATENOSECONDS) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_CLOSEONDELETE, g_plugin.getByte(YAMN_CLOSEDELETE, 0) ? BST_CHECKED : BST_UNCHECKED); - return true; - } - - bool OnApply() override - { - g_plugin.setByte(YAMN_CLOSEDELETE, IsDlgButtonChecked(m_hwnd, IDC_CLOSEONDELETE)); - g_plugin.setByte(YAMN_TTBFCHECK, IsDlgButtonChecked(m_hwnd, IDC_CHECKTTB)); - - AddTopToolbarIcon(0, 0); - - optDateTime = 0; - if (IsDlgButtonChecked(m_hwnd, IDC_LONGDATE)) optDateTime |= SHOWDATELONG; - if (IsDlgButtonChecked(m_hwnd, IDC_SMARTDATE)) optDateTime |= SHOWDATENOTODAY; - if (IsDlgButtonChecked(m_hwnd, IDC_NOSECONDS)) optDateTime |= SHOWDATENOSECONDS; - g_plugin.setByte(YAMN_DBTIMEOPTIONS, optDateTime); - return true; - } -}; - -//-------------------------------------------------------------------------------------------------- - -static int g_iStatusControls[] = {IDC_CHECKST0, IDC_CHECKST1, IDC_CHECKST2, IDC_CHECKST3, IDC_CHECKST4, IDC_CHECKST5, IDC_CHECKST6, IDC_CHECKST7}; - -static BOOL DlgShowAccountStatus(HWND hDlg, HPOP3ACCOUNT ActualAccount) -{ - if (ActualAccount) { - WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread - - CheckDlgButton(hDlg, IDC_CHECKST0, ActualAccount->StatusFlags & YAMN_ACC_ST0 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST1, ActualAccount->StatusFlags & YAMN_ACC_ST1 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST2, ActualAccount->StatusFlags & YAMN_ACC_ST2 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST3, ActualAccount->StatusFlags & YAMN_ACC_ST3 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST4, ActualAccount->StatusFlags & YAMN_ACC_ST4 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST5, ActualAccount->StatusFlags & YAMN_ACC_ST5 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST6, ActualAccount->StatusFlags & YAMN_ACC_ST6 ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST7, ActualAccount->StatusFlags & YAMN_ACC_ST7 ? BST_CHECKED : BST_UNCHECKED); - - ReadDone(ActualAccount); - } - else { - CheckDlgButton(hDlg, IDC_CHECKST0, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST1, BST_CHECKED); - CheckDlgButton(hDlg, IDC_CHECKST2, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST3, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST4, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST5, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST6, BST_UNCHECKED); - CheckDlgButton(hDlg, IDC_CHECKST7, BST_CHECKED); - } - return TRUE; -} - -static INT_PTR CALLBACK DlgProcPOP3AccStatusOpt(HWND hDlg, UINT msg, WPARAM wParam, LPARAM) -{ - static HPOP3ACCOUNT ActualAccount; - switch (msg) { - case WM_INITDIALOG: - ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput); - if (ActualAccount != nullptr) { - DlgShowAccountStatus(hDlg, ActualAccount); - for (auto &it : g_iStatusControls) - EnableWindow(GetDlgItem(hDlg, it), true); - } - else { - for (auto &it : g_iStatusControls) - CheckDlgButton(hDlg, it, BST_CHECKED); - } - TranslateDialogDefault(hDlg); - SendMessage(GetParent(hDlg), PSM_UNCHANGED, (WPARAM)hDlg, 0); - return TRUE; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDCANCEL: - EndDialog(hDlg, 0); - DestroyWindow(hDlg); - break; - - case IDOK: - int iShift = 1; - ActualAccount->StatusFlags = 0; - for (auto &it : g_iStatusControls) { - if (IsDlgButtonChecked(hDlg, it)) - ActualAccount->StatusFlags |= iShift; - iShift <<= 1; - } - - WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGESTATUSOPTION, 0, 0); - EndDialog(hDlg, 0); - DestroyWindow(hDlg); - break; - } - } - return FALSE; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Account options - -class CAccOptDlg : public CBaseOptionsDlg -{ - INT_PTR Result; - UCHAR ActualStatus; - HPOP3ACCOUNT ActualAccount = nullptr; - - CCtrlCheck chkContact, chkSsl, chkApp; - CCtrlCombo cmbAccount, cmbCP; - CCtrlButton btnStatus, btnAdd, btnDel, btnApp, btnDefault, btnReset; - - void DlgEnableAccount(bool bEnable) - { - cmbAccount.Enable(POP3Plugin->FirstAccount != nullptr); - - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECK), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITSERVER), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNAME), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPORT), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITLOGIN), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPASS), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITINTERVAL), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSND), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKMSG), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKICO), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPP), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKKBN), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNAPP), chkApp.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPP), chkApp.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPPPARAM), chkApp.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNMSGP), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFSND), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFMSG), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFICO), bEnable); - - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSTART), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFORCE), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_COMBOCP), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_STTIMELEFT), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNRESET), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEFAULT), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNSTATUS), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSSL), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPOP), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNOTLS), chkSsl.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_AUTOBODY), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACT), bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNICK), chkContact.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNOEVENT), chkContact.IsChecked() && bEnable); - } - -public: - CAccOptDlg() : - CBaseOptionsDlg(IDD_POP3ACCOUNTOPT), - cmbCP(this, IDC_COMBOCP), - btnAdd(this, IDC_BTNADD), - btnApp(this, IDC_BTNAPP), - btnDel(this, IDC_BTNDEL), - chkApp(this, IDC_CHECKAPP), - chkSsl(this, IDC_CHECKSSL), - btnReset(this, IDC_BTNRESET), - btnStatus(this, IDC_BTNSTATUS), - btnDefault(this, IDC_BTNDEFAULT), - cmbAccount(this, IDC_COMBOACCOUNT), - chkContact(this, IDC_CHECKCONTACT) - { - cmbCP.OnSelChanged = Callback(this, &CAccOptDlg::onSelChange_CP); - - cmbAccount.OnChange = Callback(this, &CAccOptDlg::onChange_Account); - cmbAccount.OnKillFocus = Callback(this, &CAccOptDlg::onKillFocus_Account); - cmbAccount.OnSelChanged = Callback(this, &CAccOptDlg::onSelChange_Account); - - chkApp.OnChange = Callback(this, &CAccOptDlg::onChangeApp); - chkSsl.OnChange = Callback(this, &CAccOptDlg::onChangeSsl); - chkContact.OnChange = Callback(this, &CAccOptDlg::onChangeContact); - - btnAdd.OnClick = Callback(this, &CAccOptDlg::onClick_Add); - btnDel.OnClick = Callback(this, &CAccOptDlg::onClick_Del); - btnApp.OnClick = Callback(this, &CAccOptDlg::onClick_App); - btnReset.OnClick = Callback(this, &CAccOptDlg::onClick_Reset); - btnStatus.OnClick = Callback(this, &CAccOptDlg::onClick_Status); - btnDefault.OnClick = Callback(this, &CAccOptDlg::onClick_Default); - } - - bool OnInitDialog() override - { - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); - - DlgEnableAccount(false); - DlgShowAccount(0); - - // Fill accounts - WaitToReadSO(POP3Plugin->AccountBrowserSO); - - for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) - if (ActualAccount->Name != nullptr) - cmbAccount.AddStringA(ActualAccount->Name); - cmbAccount.SetCurSel(0); - - ReadDoneSO(POP3Plugin->AccountBrowserSO); - - // Fill code pages - cmbCP.AddString(TranslateT("Default")); - for (int i = 1; i < CPLENSUPP; i++) { - CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[i].CP, 0, &info); - size_t len = mir_wstrlen(info.CodePageName + 7); - info.CodePageName[len + 6] = 0; - cmbCP.AddString(info.CodePageName + 7); - } - cmbCP.SetCurSel(0); - - ActualAccount = nullptr; - SendMessage(GetParent(m_hwnd), PSM_UNCHANGED, (WPARAM)m_hwnd, 0); - - WindowList_Add(pYAMNVar->MessageWnds, m_hwnd); - return true; - } - - void OnDestroy() override - { - WindowList_Remove(pYAMNVar->MessageWnds, m_hwnd); - } - - INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override - { - switch (msg) { - case WM_YAMN_CHANGESTATUS: - if ((HPOP3ACCOUNT)wParam == ActualAccount) { - wchar_t accstatus[256]; - GetAccountStatus(ActualAccount, accstatus); - SetDlgItemText(m_hwnd, IDC_STSTATUS, accstatus); - return TRUE; - } - break; - - case WM_YAMN_CHANGESTATUSOPTION: - NotifyChange(); - return TRUE; - - case WM_YAMN_CHANGETIME: - if ((HPOP3ACCOUNT)wParam == ActualAccount) { - wchar_t Text[256]; - mir_snwprintf(Text, TranslateT("Time left to next check [s]: %d"), (uint32_t)lParam); - SetDlgItemText(m_hwnd, IDC_STTIMELEFT, Text); - } - return TRUE; - } - return CDlgBase::DlgProc(msg, wParam, lParam); - } - - void onChange_Account(CCtrlCombo *) - { - ActualAccount = nullptr; - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - DlgEnableAccount(0 != GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput))); - } - - void onKillFocus_Account(CCtrlCombo *) - { - GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); - if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput))) { - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); - DlgEnableAccount(mir_strlen(DlgInput) > 0); - } - else { - DlgShowAccount(ActualAccount); - DlgEnableAccount(true); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); - } - } - - void onSelChange_Account(CCtrlCombo *) - { - if (CB_ERR != (Result = cmbAccount.GetCurSel())) - SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_GETLBTEXT, (WPARAM)Result, (LPARAM)DlgInput); - - if ((Result == CB_ERR) || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) { - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); - } - else { - DlgShowAccount(ActualAccount); - DlgEnableAccount(true); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); - } - } - - void onSelChange_CP(CCtrlCombo *) - { - int sel = cmbCP.GetCurSel(); - CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[sel].CP, 0, &info); - DlgSetItemTextW(m_hwnd, IDC_STSTATUS, info.CodePageName); - } - - void onChangeContact(CCtrlCheck *) - { - bool bEnabled = chkContact.IsChecked(); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNICK), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNOEVENT), bEnabled); - } - - void onChangeSsl(CCtrlCheck *) - { - bool bEnabled = chkSsl.IsChecked(); - SetDlgItemInt(m_hwnd, IDC_EDITPORT, bEnabled ? 995 : 110, FALSE); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNOTLS), !bEnabled); - } - - void onChangeApp(CCtrlCheck *) - { - bool bEnabled = chkApp.IsChecked(); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNAPP), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPP), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPPPARAM), bEnabled); - } - - void onClick_Status(CCtrlButton *) - { - DialogBoxParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_CHOOSESTATUSMODES), m_hwnd, DlgProcPOP3AccStatusOpt, NULL); - } - - void onClick_Add(CCtrlButton *) - { - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - DlgShowAccount(0); - DlgEnableAccount(true); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); - DlgSetItemTextW(m_hwnd, IDC_EDITNAME, TranslateT("New Account")); - - int index = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_ADDSTRING, 0, (LPARAM)TranslateT("New Account")); - if (index != CB_ERR && index != CB_ERRSPACE) - SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_SETCURSEL, index, (LPARAM)TranslateT("New Account")); - } - - void onClick_App(CCtrlButton *) - { - wchar_t filter[MAX_PATH]; - mir_snwprintf(filter, L"%s (*.exe;*.bat;*.cmd;*.com)%c*.exe;*.bat;*.cmd;*.com%c%s (*.*)%c*.*%c", - TranslateT("Executables"), 0, 0, TranslateT("All Files"), 0, 0); - - OPENFILENAME OFNStruct = {0}; - OFNStruct.lStructSize = sizeof(OPENFILENAME); - OFNStruct.hwndOwner = m_hwnd; - OFNStruct.lpstrFilter = filter; - OFNStruct.nFilterIndex = 1; - OFNStruct.nMaxFile = MAX_PATH; - OFNStruct.lpstrFile = new wchar_t[MAX_PATH]; - OFNStruct.lpstrFile[0] = (wchar_t)0; - OFNStruct.lpstrTitle = TranslateT("Select executable used for notification"); - OFNStruct.Flags = OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR; - if (!GetOpenFileName(&OFNStruct)) { - if (CommDlgExtendedError()) - MessageBox(m_hwnd, TranslateT("Dialog box error"), TranslateT("Failed"), MB_OK); - } - else DlgSetItemTextW(m_hwnd, IDC_EDITAPP, OFNStruct.lpstrFile); - delete[] OFNStruct.lpstrFile; - } - - void onClick_Default(CCtrlButton *) - { - DlgShowAccount(0); - } - - void onClick_Del(CCtrlButton *) - { - GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); - if ((CB_ERR == (Result = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0))) - || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) - return; - - if (IDOK != MessageBox(m_hwnd, TranslateT("Do you really want to delete this account?"), TranslateT("Delete account confirmation"), MB_OKCANCEL | MB_ICONWARNING)) - return; - - DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); - - if (ActualAccount->hContact != NULL) - db_delete_contact(ActualAccount->hContact); - - CallService(MS_YAMN_DELETEACCOUNT, (WPARAM)POP3Plugin, (LPARAM)ActualAccount); - - // We can consider our account as deleted. - SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_DELETESTRING, Result, 0); - DlgSetItemText(m_hwnd, IDC_COMBOACCOUNT, nullptr); - DlgEnableAccount(false); - DlgShowAccount(0); - } - - void onClick_Reset(CCtrlButton *) - { - if (ActualAccount != nullptr) - ActualAccount->TimeLeft = ActualAccount->Interval; - } - - bool OnApply() override - { - char Text[MAX_PATH]; - wchar_t TextW[MAX_PATH]; - BOOL Translated, NewAcc = FALSE; - size_t Length, index; - - if (!GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text))) - return false; - - BOOL Check = (IsDlgButtonChecked(m_hwnd, IDC_CHECK) == BST_CHECKED); - BOOL CheckSSL = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSSL) == BST_CHECKED); - BOOL CheckNoTLS = (IsDlgButtonChecked(m_hwnd, IDC_CHECKNOTLS) == BST_CHECKED); - BOOL CheckAPOP = (IsDlgButtonChecked(m_hwnd, IDC_CHECKAPOP) == BST_CHECKED); - - BOOL CheckABody = (IsDlgButtonChecked(m_hwnd, IDC_AUTOBODY) == BST_CHECKED); - BOOL CheckMsg = (IsDlgButtonChecked(m_hwnd, IDC_CHECKMSG) == BST_CHECKED); - BOOL CheckSnd = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSND) == BST_CHECKED); - BOOL CheckIco = (IsDlgButtonChecked(m_hwnd, IDC_CHECKICO) == BST_CHECKED); - - BOOL CheckApp = (IsDlgButtonChecked(m_hwnd, IDC_CHECKAPP) == BST_CHECKED); - BOOL CheckKBN = (IsDlgButtonChecked(m_hwnd, IDC_CHECKKBN) == BST_CHECKED); - BOOL CheckContact = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACT) == BST_CHECKED); - BOOL CheckContactNick = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACTNICK) == BST_CHECKED); - BOOL CheckContactNoEvent = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACTNOEVENT) == BST_CHECKED); - - BOOL CheckFSnd = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFSND) == BST_CHECKED); - BOOL CheckFMsg = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFMSG) == BST_CHECKED); - BOOL CheckFIco = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFICO) == BST_CHECKED); - - BOOL CheckNMsgP = (IsDlgButtonChecked(m_hwnd, IDC_CHECKNMSGP) == BST_CHECKED); - - UINT Port = GetDlgItemInt(m_hwnd, IDC_EDITPORT, &Translated, FALSE); - if (!Translated) { - MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); - SetFocus(GetDlgItem(m_hwnd, IDC_EDITPORT)); - return false; - } - - UINT Interval = GetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, &Translated, FALSE); - if (!Translated) { - MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); - SetFocus(GetDlgItem(m_hwnd, IDC_EDITINTERVAL)); - return false; - } - - GetDlgItemTextA(m_hwnd, IDC_EDITAPP, Text, _countof(Text)); - if (CheckApp && !(Length = mir_strlen(Text))) { - MessageBox(m_hwnd, TranslateT("Please select application to run"), TranslateT("Input error"), MB_OK); - return false; - } - - GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text)); - if (!(Length = mir_strlen(Text))) { - GetDlgItemTextA(m_hwnd, IDC_EDITNAME, Text, _countof(Text)); - if (!(Length = mir_strlen(Text))) - return false; - } - - DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); - - if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)Text))) { - NewAcc = TRUE; - WaitToWriteSO(POP3Plugin->AccountBrowserSO); - if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)POP3Plugin, (LPARAM)YAMN_ACCOUNTVERSION))) { - WriteDoneSO(POP3Plugin->AccountBrowserSO); - MessageBox(m_hwnd, TranslateT("Cannot allocate memory space for new account"), TranslateT("Memory error"), MB_OK); - return false; - } - DlgEnableAccount(true); - } - else { // We have to get full access to AccountBrowser, so other iterating thrads cannot get new account until new account is right set - WaitToWriteSO(POP3Plugin->AccountBrowserSO); - } - - if (WAIT_OBJECT_0 != WaitToWrite(ActualAccount)) - WriteDoneSO(POP3Plugin->AccountBrowserSO); - - GetDlgItemTextA(m_hwnd, IDC_EDITNAME, Text, _countof(Text)); - if (!(Length = mir_strlen(Text))) - return false; - if (nullptr != ActualAccount->Name) - delete[] ActualAccount->Name; - ActualAccount->Name = new char[mir_strlen(Text) + 1]; - mir_strcpy(ActualAccount->Name, Text); - - GetDlgItemTextA(m_hwnd, IDC_EDITSERVER, Text, _countof(Text)); - if (nullptr != ActualAccount->Server->Name) - delete[] ActualAccount->Server->Name; - ActualAccount->Server->Name = new char[mir_strlen(Text) + 1]; - mir_strcpy(ActualAccount->Server->Name, Text); - - GetDlgItemTextA(m_hwnd, IDC_EDITLOGIN, Text, _countof(Text)); - if (nullptr != ActualAccount->Server->Login) - delete[] ActualAccount->Server->Login; - ActualAccount->Server->Login = new char[mir_strlen(Text) + 1]; - mir_strcpy(ActualAccount->Server->Login, Text); - - GetDlgItemTextA(m_hwnd, IDC_EDITPASS, Text, _countof(Text)); - if (nullptr != ActualAccount->Server->Passwd) - delete[] ActualAccount->Server->Passwd; - ActualAccount->Server->Passwd = new char[mir_strlen(Text) + 1]; - mir_strcpy(ActualAccount->Server->Passwd, Text); - - GetDlgItemTextW(m_hwnd, IDC_EDITAPP, TextW, _countof(TextW)); - if (nullptr != ActualAccount->NewMailN.App) - delete[] ActualAccount->NewMailN.App; - ActualAccount->NewMailN.App = new wchar_t[mir_wstrlen(TextW) + 1]; - mir_wstrcpy(ActualAccount->NewMailN.App, TextW); - - GetDlgItemTextW(m_hwnd, IDC_EDITAPPPARAM, TextW, _countof(TextW)); - if (nullptr != ActualAccount->NewMailN.AppParam) - delete[] ActualAccount->NewMailN.AppParam; - ActualAccount->NewMailN.AppParam = new wchar_t[mir_wstrlen(TextW) + 1]; - mir_wstrcpy(ActualAccount->NewMailN.AppParam, TextW); - - ActualAccount->Server->Port = Port; - ActualAccount->Interval = Interval * 60; - - if (CB_ERR == (index = SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_GETCURSEL, 0, 0))) - index = CPDEFINDEX; - ActualAccount->CP = CodePageNamesSupp[index].CP; - - if (NewAcc) - ActualAccount->TimeLeft = Interval * 60; - - BOOL CheckStart = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSTART) == BST_CHECKED); - BOOL CheckForce = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFORCE) == BST_CHECKED); - - ActualAccount->Flags = - (Check ? YAMN_ACC_ENA : 0) | - (CheckSSL ? YAMN_ACC_SSL23 : 0) | - (CheckNoTLS ? YAMN_ACC_NOTLS : 0) | - (CheckAPOP ? YAMN_ACC_APOP : 0) | - (CheckABody ? YAMN_ACC_BODY : 0) | - (ActualAccount->Flags & YAMN_ACC_POPN); - - ActualAccount->StatusFlags &= 0xFFFF; - ActualAccount->StatusFlags |= - (CheckStart ? YAMN_ACC_STARTS : 0) | - (CheckForce ? YAMN_ACC_FORCE : 0); - - ActualAccount->NewMailN.Flags = - (CheckSnd ? YAMN_ACC_SND : 0) | - (CheckMsg ? YAMN_ACC_MSG : 0) | - (CheckIco ? YAMN_ACC_ICO : 0) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_POP) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_POPC) | - (CheckApp ? YAMN_ACC_APP : 0) | - (CheckKBN ? YAMN_ACC_KBN : 0) | - (CheckContact ? YAMN_ACC_CONT : 0) | - (CheckContactNick ? YAMN_ACC_CONTNICK : 0) | - (CheckContactNoEvent ? YAMN_ACC_CONTNOEVENT : 0) | - YAMN_ACC_MSGP; //this is default: when new mail arrives and window was displayed, leave it displayed. - - ActualAccount->NoNewMailN.Flags = - (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POP) | - (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC) | - (CheckNMsgP ? YAMN_ACC_MSGP : 0); - - ActualAccount->BadConnectN.Flags = - (CheckFSnd ? YAMN_ACC_SND : 0) | - (CheckFMsg ? YAMN_ACC_MSG : 0) | - (CheckFIco ? YAMN_ACC_ICO : 0) | - (ActualAccount->BadConnectN.Flags & YAMN_ACC_POP) | - (ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC); - - WriteDone(ActualAccount); - WriteDoneSO(POP3Plugin->AccountBrowserSO); - - EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); - - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - - index = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0); - - HPOP3ACCOUNT temp = ActualAccount; - - SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_RESETCONTENT, 0, 0); - if (POP3Plugin->FirstAccount != nullptr) - for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) - if (ActualAccount->Name != nullptr) - SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_ADDSTRING, 0, (LPARAM)ActualAccount->Name); - - ActualAccount = temp; - SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_SETCURSEL, (WPARAM)index, (LPARAM)ActualAccount->Name); - - WritePOP3Accounts(); - RefreshContact(); - return TRUE; - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Popup options - -class CPopupOptsDlg : public CBaseOptionsDlg -{ - HPOP3ACCOUNT ActualAccount = nullptr; - UCHAR ActualStatus; - - CCtrlCombo cmbAccount, cmbCP; - CCtrlCheck chkCol, chkFcol, chkNcol, chkPop, chkFpop, chkNpop; - CCtrlButton btnPreview; - - void DlgShowAccountPopup() - { - if (ActualAccount) { - WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread - SetDlgItemInt(m_hwnd, IDC_EDITPOPS, ActualAccount->NewMailN.PopupTime, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, ActualAccount->NoNewMailN.PopupTime, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, ActualAccount->BadConnectN.PopupTime, FALSE); - - chkPop.SetState(ActualAccount->NewMailN.Flags & YAMN_ACC_POP); - chkCol.SetState(ActualAccount->NewMailN.Flags & YAMN_ACC_POPC); - chkNpop.SetState(ActualAccount->NoNewMailN.Flags & YAMN_ACC_POP); - chkNcol.SetState(ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC); - chkFpop.SetState(ActualAccount->BadConnectN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); - chkFcol.SetState(ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC); - CheckDlgButton(m_hwnd, IDC_RADIOPOPN, ActualAccount->Flags & YAMN_ACC_POPN ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOP1, ActualAccount->Flags & YAMN_ACC_POPN ? BST_UNCHECKED : BST_CHECKED); - ReadDone(ActualAccount); - } - else { // default - SetDlgItemInt(m_hwnd, IDC_EDITPOPS, 0, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, 0, FALSE); - SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, 0, FALSE); - chkPop.SetState(true); - chkCol.SetState(true); - chkNpop.SetState(true); - chkNcol.SetState(true); - chkFpop.SetState(true); - chkFcol.SetState(true); - CheckDlgButton(m_hwnd, IDC_RADIOPOPN, BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_RADIOPOP1, BST_CHECKED); - } - } - - void DlgEnableAccountPopup(bool bEnable) - { - chkPop.Enable(bEnable); - chkCol.Enable(chkPop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPOPS), chkPop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && chkPop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && chkPop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOPN), chkPop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOP1), chkPop.IsChecked() && bEnable); - - chkNpop.Enable(bEnable); - chkNcol.Enable(chkNpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNPOPS), chkNpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && chkNpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && chkNpop.IsChecked() && bEnable); - - chkFpop.Enable(bEnable); - chkFcol.Enable(chkFpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITFPOPS), chkFpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && chkFpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && chkFpop.IsChecked() && bEnable); - EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPOP), bEnable); - } - - void DlgShowAccountColors() - { - WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread - - if (ActualAccount->NewMailN.Flags & YAMN_ACC_POPC) { - SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NewMailN.PopupB); - SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NewMailN.PopupT); - } - else { - SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); - SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); - } - if (ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC) { - SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->BadConnectN.PopupB); - SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->BadConnectN.PopupT); - } - else { - SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); - SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); - } - if (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC) { - SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NoNewMailN.PopupB); - SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NoNewMailN.PopupT); - } - else { - SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); - SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); - } - - ReadDone(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread - } - -public: - CPopupOptsDlg() : - CBaseOptionsDlg(IDD_POP3ACCOUNTPOPUP), - cmbCP(this, IDC_COMBOCP), - chkCol(this, IDC_CHECKCOL), - chkPop(this, IDC_CHECKPOP), - chkFcol(this, IDC_CHECKFCOL), - chkFpop(this, IDC_CHECKFPOP), - chkNcol(this, IDC_CHECKNCOL), - chkNpop(this, IDC_CHECKNPOP), - btnPreview(this, IDC_PREVIEW), - cmbAccount(this, IDC_COMBOACCOUNT) - { - chkPop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Pop); - chkFpop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Fpop); - chkNpop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Npop); - chkCol.OnChange = chkFcol.OnChange = chkNcol.OnChange = Callback(this, &CPopupOptsDlg::onChange_Col); - - cmbCP.OnSelChanged = Callback(this, &CPopupOptsDlg::onSelChange_CP); - - btnPreview.OnClick = Callback(this, &CPopupOptsDlg::onClick_Preview); - - cmbAccount.OnKillFocus = Callback(this, &CPopupOptsDlg::onKillFocus_Account); - cmbAccount.OnSelChanged = Callback(this, &CPopupOptsDlg::onSelChange_Account); - } - - bool OnInitDialog() override - { - WindowList_Add(pYAMNVar->MessageWnds, m_hwnd); - - DlgEnableAccountPopup(false); - DlgShowAccountPopup(); - - WaitToReadSO(POP3Plugin->AccountBrowserSO); - - if (POP3Plugin->FirstAccount != nullptr) - for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) - if (ActualAccount->Name != nullptr) - cmbAccount.AddStringA(ActualAccount->Name); - - ReadDoneSO(POP3Plugin->AccountBrowserSO); - ActualAccount = nullptr; - cmbAccount.SetCurSel(0); - return true; - } - - void OnDestroy() override - { - WindowList_Remove(pYAMNVar->MessageWnds, m_hwnd); - } - - void onKillFocus_Account(CCtrlCombo *) - { - GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); - if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput))) { - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - if (mir_strlen(DlgInput)) - DlgEnableAccountPopup(true); - else - DlgEnableAccountPopup(false); - } - else { - DlgShowAccount(ActualAccount); - DlgShowAccountColors(); - DlgEnableAccountPopup(true); - } - } - - void onSelChange_Account(CCtrlCombo *) - { - int Result = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0); - if (CB_ERR != Result) - SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_GETLBTEXT, (WPARAM)Result, (LPARAM)DlgInput); - if ((Result == CB_ERR) || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) { - DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); - } - else { - DlgShowAccount(ActualAccount); - DlgShowAccountColors(); - DlgEnableAccountPopup(true); - } - } - - void onSelChange_CP(CCtrlCombo *) - { - int sel = SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_GETCURSEL, 0, 0); - CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[sel].CP, 0, &info); - DlgSetItemTextW(m_hwnd, IDC_STSTATUS, info.CodePageName); - } - - void onChange_Col(CCtrlCheck *) - { - EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && chkPop.IsChecked()); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && chkPop.IsChecked()); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && chkNpop.IsChecked()); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && chkNpop.IsChecked()); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && chkFpop.IsChecked()); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && chkFpop.IsChecked()); - } - - void onClick_Preview(CCtrlButton *) - { - if (chkPop.IsChecked()) { - POPUPDATAW Tester = {}; - Tester.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); - mir_wstrncpy(Tester.lpwzContactName, TranslateT("Account Test"), MAX_CONTACTNAME); - mir_wstrncpy(Tester.lpwzText, TranslateT("You have N new mail messages"), MAX_SECONDLINE); - if (chkCol.IsChecked()) { - Tester.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_GETCOLOUR, 0, 0); - Tester.colorText = SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_GETCOLOUR, 0, 0); - } - else { - Tester.colorBack = GetSysColor(COLOR_BTNFACE); - Tester.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - PUAddPopupW(&Tester); - } - - if (chkFpop.IsChecked()) { - POPUPDATAW TesterF = {}; - TesterF.lchIcon = g_plugin.getIcon(IDI_BADCONNECT); - mir_wstrncpy(TesterF.lpwzContactName, TranslateT("Account Test (failed)"), MAX_CONTACTNAME); - mir_wstrncpy(TesterF.lpwzText, TranslateT("Connection failed message"), MAX_SECONDLINE); - if (chkFcol.IsChecked()) { - TesterF.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_GETCOLOUR, 0, 0); - TesterF.colorText = SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_GETCOLOUR, 0, 0); - } - else { - TesterF.colorBack = GetSysColor(COLOR_BTNFACE); - TesterF.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - PUAddPopupW(&TesterF); - } - - if (chkNpop.IsChecked()) { - POPUPDATAW TesterN = {}; - TesterN.lchIcon = g_plugin.getIcon(IDI_LAUNCHAPP); - mir_wstrncpy(TesterN.lpwzContactName, TranslateT("Account Test"), MAX_CONTACTNAME); - mir_wstrncpy(TesterN.lpwzText, TranslateT("No new mail message"), MAX_SECONDLINE); - if (chkNcol.IsChecked()) { - TesterN.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_GETCOLOUR, 0, 0); - TesterN.colorText = SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_GETCOLOUR, 0, 0); - } - else { - TesterN.colorBack = GetSysColor(COLOR_BTNFACE); - TesterN.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - PUAddPopupW(&TesterN); - } - } - - void onChange_Pop(CCtrlCheck *) - { - bool bEnabled = chkPop.IsChecked(); - chkCol.Enable(bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOPN), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOP1), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPOPS), bEnabled); - } - - void onChange_Fpop(CCtrlCheck *) - { - bool bEnabled = chkFpop.IsChecked(); - chkFcol.Enable(bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITFPOPS), bEnabled); - } - - void onChange_Npop(CCtrlCheck *) - { - bool bEnabled = chkNpop.IsChecked(); - chkNcol.Enable(bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNPOPS), bEnabled); - } - - bool OnApply() override - { - wchar_t Text[MAX_PATH]; - if (!GetDlgItemText(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text))) - return false; - - BOOL CheckPopup = chkPop.IsChecked(); - BOOL CheckPopupW = chkCol.IsChecked(); - - BOOL CheckFPopup = chkFpop.IsChecked(); - BOOL CheckFPopupW = chkFcol.IsChecked(); - - BOOL CheckNPopup = chkNpop.IsChecked(); - BOOL CheckNPopupW = chkNcol.IsChecked(); - - BOOL CheckPopN = (IsDlgButtonChecked(m_hwnd, IDC_RADIOPOPN) == BST_CHECKED); - - BOOL Translated; - UINT Time = GetDlgItemInt(m_hwnd, IDC_EDITPOPS, &Translated, FALSE); - if (!Translated) { - MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); - SetFocus(GetDlgItem(m_hwnd, IDC_EDITPOPS)); - return false; - } - UINT TimeN = GetDlgItemInt(m_hwnd, IDC_EDITNPOPS, &Translated, FALSE); - if (!Translated) { - MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); - SetFocus(GetDlgItem(m_hwnd, IDC_EDITNPOPS)); - return false; - } - UINT TimeF = GetDlgItemInt(m_hwnd, IDC_EDITFPOPS, &Translated, FALSE); - if (!Translated) { - MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); - SetFocus(GetDlgItem(m_hwnd, IDC_EDITFPOPS)); - return false; - } - - DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); - - ActualAccount->Flags = - (ActualAccount->Flags & YAMN_ACC_ENA) | - (ActualAccount->Flags & YAMN_ACC_SSL23) | - (ActualAccount->Flags & YAMN_ACC_NOTLS) | - (ActualAccount->Flags & YAMN_ACC_APOP) | - (ActualAccount->Flags & YAMN_ACC_BODY) | - (CheckPopN ? YAMN_ACC_POPN : 0); - - ActualAccount->NewMailN.Flags = - (ActualAccount->NewMailN.Flags & YAMN_ACC_SND) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_MSG) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_ICO) | - (CheckPopup ? YAMN_ACC_POP : 0) | - (CheckPopupW ? YAMN_ACC_POPC : 0) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_APP) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_KBN) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_CONT) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_CONTNICK) | - (ActualAccount->NewMailN.Flags & YAMN_ACC_CONTNOEVENT) | - YAMN_ACC_MSGP; - - ActualAccount->NoNewMailN.Flags = - (CheckNPopup ? YAMN_ACC_POP : 0) | - (CheckNPopupW ? YAMN_ACC_POPC : 0) | - (ActualAccount->NoNewMailN.Flags & YAMN_ACC_MSGP); - - ActualAccount->BadConnectN.Flags = - (ActualAccount->BadConnectN.Flags & YAMN_ACC_SND) | - (ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) | - (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) | - (CheckFPopup ? YAMN_ACC_POP : 0) | - (CheckFPopupW ? YAMN_ACC_POPC : 0); - - ActualAccount->NewMailN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_GETCOLOUR, 0, 0); - ActualAccount->NewMailN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_GETCOLOUR, 0, 0); - ActualAccount->NewMailN.PopupTime = Time; - - ActualAccount->NoNewMailN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_GETCOLOUR, 0, 0); - ActualAccount->NoNewMailN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_GETCOLOUR, 0, 0); - ActualAccount->NoNewMailN.PopupTime = TimeN; - - ActualAccount->BadConnectN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_GETCOLOUR, 0, 0); - ActualAccount->BadConnectN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_GETCOLOUR, 0, 0); - ActualAccount->BadConnectN.PopupTime = TimeF; - - WriteDone(ActualAccount); - WriteDoneSO(POP3Plugin->AccountBrowserSO); - - WritePOP3Accounts(); - RefreshContact(); - return TRUE; - } -}; - -//-------------------------------------------------------------------------------------------------- - -int YAMNOptInitSvc(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szGroup.a = LPGEN("Network"); - odp.szTitle.a = LPGEN("YAMN"); - odp.flags = ODPF_BOLDGROUPS; - - odp.szTab.a = LPGEN("Accounts"); - odp.pDialog = new CAccOptDlg(); - g_plugin.addOptions(wParam, &odp); - - odp.szTab.a = LPGEN("General"); - odp.pDialog = new CGeneralOptDlg(); - g_plugin.addOptions(wParam, &odp); - - odp.szGroup.a = LPGEN("Popups"); - odp.szTab.a = LPGEN("YAMN"); - odp.pDialog = new CPopupOptsDlg(); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/* + * This code implements POP3 options window handling + * + * (c) majvan 2002-2003 +*/ + +#include "../../stdafx.h" + +//-------------------------------------------------------------------------------------------------- + +static char DlgInput[MAX_PATH]; + +static BOOL DlgSetItemText(HWND hDlg, WPARAM wParam, const char *str) +{ + if (str == nullptr) + SetDlgItemTextA(hDlg, wParam, ""); + else + SetDlgItemTextA(hDlg, wParam, str); + return TRUE; +} + +static BOOL DlgSetItemTextW(HWND hDlg, WPARAM wParam, const wchar_t *str) +{ + if (str == nullptr) + SetDlgItemTextW(hDlg, wParam, L""); + else + SetDlgItemTextW(hDlg, wParam, str); + return TRUE; +} + +struct CBaseOptionsDlg : public CDlgBase +{ + CBaseOptionsDlg(int iDlgId) : + CDlgBase(g_plugin, iDlgId) + {} + + void DlgShowAccount(HPOP3ACCOUNT pAccount) + { + int i; + + if (pAccount) { + // we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread + WaitToRead(pAccount); + + DlgSetItemText(m_hwnd, IDC_EDITSERVER, pAccount->Server->Name); + DlgSetItemText(m_hwnd, IDC_EDITNAME, pAccount->Name); + DlgSetItemText(m_hwnd, IDC_EDITLOGIN, pAccount->Server->Login); + DlgSetItemText(m_hwnd, IDC_EDITPASS, pAccount->Server->Passwd); + DlgSetItemTextW(m_hwnd, IDC_EDITAPP, pAccount->NewMailN.App); + DlgSetItemTextW(m_hwnd, IDC_EDITAPPPARAM, pAccount->NewMailN.AppParam); + SetDlgItemInt(m_hwnd, IDC_EDITPORT, pAccount->Server->Port, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, pAccount->Interval / 60, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITPOPS, pAccount->NewMailN.PopupTime, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, pAccount->NoNewMailN.PopupTime, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, pAccount->BadConnectN.PopupTime, FALSE); + for (i = 0; i <= CPLENSUPP; i++) + if ((i < CPLENSUPP) && (CodePageNamesSupp[i].CP == pAccount->CP)) { + SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)i, 0); + break; + } + + if (i == CPLENSUPP) + SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)CPDEFINDEX, 0); + + CheckDlgButton(m_hwnd, IDC_CHECK, pAccount->Flags & YAMN_ACC_ENA ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSND, pAccount->NewMailN.Flags & YAMN_ACC_SND ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKMSG, pAccount->NewMailN.Flags & YAMN_ACC_MSG ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKICO, pAccount->NewMailN.Flags & YAMN_ACC_ICO ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKPOP, pAccount->NewMailN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCOL, pAccount->NewMailN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKAPP, pAccount->NewMailN.Flags & YAMN_ACC_APP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKKBN, pAccount->NewMailN.Flags & YAMN_ACC_KBN ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKNPOP, pAccount->NoNewMailN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKNCOL, pAccount->NoNewMailN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKNMSGP, pAccount->NoNewMailN.Flags & YAMN_ACC_MSGP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFSND, pAccount->BadConnectN.Flags & YAMN_ACC_SND ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFMSG, pAccount->BadConnectN.Flags & YAMN_ACC_MSG ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFICO, pAccount->BadConnectN.Flags & YAMN_ACC_ICO ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFPOP, pAccount->BadConnectN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFCOL, pAccount->BadConnectN.Flags & YAMN_ACC_POPC ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOPN, pAccount->Flags & YAMN_ACC_POPN ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOP1, pAccount->Flags & YAMN_ACC_POPN ? BST_UNCHECKED : BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSSL, pAccount->Flags & YAMN_ACC_SSL23 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKNOTLS, pAccount->Flags & YAMN_ACC_NOTLS ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKAPOP, pAccount->Flags & YAMN_ACC_APOP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_AUTOBODY, pAccount->Flags & YAMN_ACC_BODY ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSTART, pAccount->StatusFlags & YAMN_ACC_STARTS ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFORCE, pAccount->StatusFlags & YAMN_ACC_FORCE ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCONTACT, pAccount->NewMailN.Flags & YAMN_ACC_CONT ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCONTACTNICK, pAccount->NewMailN.Flags & YAMN_ACC_CONTNICK ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCONTACTNOEVENT, pAccount->NewMailN.Flags & YAMN_ACC_CONTNOEVENT ? BST_CHECKED : BST_UNCHECKED); + + wchar_t accstatus[256]; + GetAccountStatus(pAccount, accstatus); + SetDlgItemText(m_hwnd, IDC_STSTATUS, accstatus); + ReadDone(pAccount); + } + else { + DlgSetItemText(m_hwnd, IDC_EDITSERVER, nullptr); + DlgSetItemText(m_hwnd, IDC_EDITNAME, nullptr); + DlgSetItemText(m_hwnd, IDC_EDITLOGIN, nullptr); + DlgSetItemText(m_hwnd, IDC_EDITPASS, nullptr); + DlgSetItemText(m_hwnd, IDC_EDITAPP, nullptr); + DlgSetItemText(m_hwnd, IDC_EDITAPPPARAM, nullptr); + DlgSetItemText(m_hwnd, IDC_STTIMELEFT, nullptr); + SetDlgItemInt(m_hwnd, IDC_EDITPORT, 110, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, 30, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITPOPS, 0, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, 0, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, 0, FALSE); + SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_SETCURSEL, (WPARAM)CPDEFINDEX, 0); + CheckDlgButton(m_hwnd, IDC_CHECK, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSND, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKMSG, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKICO, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKPOP, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCOL, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKAPP, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFSND, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFMSG, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFICO, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFPOP, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFCOL, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSTART, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKFORCE, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOPN, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOP1, BST_CHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKSSL, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKNOTLS, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKAPOP, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_AUTOBODY, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CHECKCONTACT, BST_CHECKED); + + SetDlgItemText(m_hwnd, IDC_STSTATUS, TranslateT("No account selected")); + } + } +}; + +//======================================================================================= +// General options dialog + +struct CGeneralOptDlg : public CBaseOptionsDlg +{ + CGeneralOptDlg() : + CBaseOptionsDlg(IDD_YAMNOPT) + {} + + bool OnInitDialog() override + { + CheckDlgButton(m_hwnd, IDC_CHECKTTB, g_plugin.getByte(YAMN_TTBFCHECK, 1) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_LONGDATE, (optDateTime & SHOWDATELONG) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_SMARTDATE, (optDateTime & SHOWDATENOTODAY) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_NOSECONDS, (optDateTime & SHOWDATENOSECONDS) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_CLOSEONDELETE, g_plugin.getByte(YAMN_CLOSEDELETE, 0) ? BST_CHECKED : BST_UNCHECKED); + return true; + } + + bool OnApply() override + { + g_plugin.setByte(YAMN_CLOSEDELETE, IsDlgButtonChecked(m_hwnd, IDC_CLOSEONDELETE)); + g_plugin.setByte(YAMN_TTBFCHECK, IsDlgButtonChecked(m_hwnd, IDC_CHECKTTB)); + + AddTopToolbarIcon(0, 0); + + optDateTime = 0; + if (IsDlgButtonChecked(m_hwnd, IDC_LONGDATE)) optDateTime |= SHOWDATELONG; + if (IsDlgButtonChecked(m_hwnd, IDC_SMARTDATE)) optDateTime |= SHOWDATENOTODAY; + if (IsDlgButtonChecked(m_hwnd, IDC_NOSECONDS)) optDateTime |= SHOWDATENOSECONDS; + g_plugin.setByte(YAMN_DBTIMEOPTIONS, optDateTime); + return true; + } +}; + +//-------------------------------------------------------------------------------------------------- + +static int g_iStatusControls[] = {IDC_CHECKST0, IDC_CHECKST1, IDC_CHECKST2, IDC_CHECKST3, IDC_CHECKST4, IDC_CHECKST5, IDC_CHECKST6, IDC_CHECKST7}; + +static BOOL DlgShowAccountStatus(HWND hDlg, HPOP3ACCOUNT ActualAccount) +{ + if (ActualAccount) { + WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread + + CheckDlgButton(hDlg, IDC_CHECKST0, ActualAccount->StatusFlags & YAMN_ACC_ST0 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST1, ActualAccount->StatusFlags & YAMN_ACC_ST1 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST2, ActualAccount->StatusFlags & YAMN_ACC_ST2 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST3, ActualAccount->StatusFlags & YAMN_ACC_ST3 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST4, ActualAccount->StatusFlags & YAMN_ACC_ST4 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST5, ActualAccount->StatusFlags & YAMN_ACC_ST5 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST6, ActualAccount->StatusFlags & YAMN_ACC_ST6 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST7, ActualAccount->StatusFlags & YAMN_ACC_ST7 ? BST_CHECKED : BST_UNCHECKED); + + ReadDone(ActualAccount); + } + else { + CheckDlgButton(hDlg, IDC_CHECKST0, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST1, BST_CHECKED); + CheckDlgButton(hDlg, IDC_CHECKST2, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST3, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST4, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST5, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST6, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_CHECKST7, BST_CHECKED); + } + return TRUE; +} + +static INT_PTR CALLBACK DlgProcPOP3AccStatusOpt(HWND hDlg, UINT msg, WPARAM wParam, LPARAM) +{ + static HPOP3ACCOUNT ActualAccount; + switch (msg) { + case WM_INITDIALOG: + ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput); + if (ActualAccount != nullptr) { + DlgShowAccountStatus(hDlg, ActualAccount); + for (auto &it : g_iStatusControls) + EnableWindow(GetDlgItem(hDlg, it), true); + } + else { + for (auto &it : g_iStatusControls) + CheckDlgButton(hDlg, it, BST_CHECKED); + } + TranslateDialogDefault(hDlg); + SendMessage(GetParent(hDlg), PSM_UNCHANGED, (WPARAM)hDlg, 0); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + EndDialog(hDlg, 0); + DestroyWindow(hDlg); + break; + + case IDOK: + int iShift = 1; + ActualAccount->StatusFlags = 0; + for (auto &it : g_iStatusControls) { + if (IsDlgButtonChecked(hDlg, it)) + ActualAccount->StatusFlags |= iShift; + iShift <<= 1; + } + + WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGESTATUSOPTION, 0, 0); + EndDialog(hDlg, 0); + DestroyWindow(hDlg); + break; + } + } + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Account options + +class CAccOptDlg : public CBaseOptionsDlg +{ + INT_PTR Result; + UCHAR ActualStatus; + HPOP3ACCOUNT ActualAccount = nullptr; + + CCtrlCheck chkContact, chkSsl, chkApp; + CCtrlCombo cmbAccount, cmbCP; + CCtrlButton btnStatus, btnAdd, btnDel, btnApp, btnDefault, btnReset; + + void DlgEnableAccount(bool bEnable) + { + cmbAccount.Enable(POP3Plugin->FirstAccount != nullptr); + + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECK), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITSERVER), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNAME), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPORT), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITLOGIN), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPASS), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITINTERVAL), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSND), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKMSG), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKICO), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPP), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKKBN), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNAPP), chkApp.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPP), chkApp.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPPPARAM), chkApp.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNMSGP), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFSND), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFMSG), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFICO), bEnable); + + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSTART), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKFORCE), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_COMBOCP), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_STTIMELEFT), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNRESET), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEFAULT), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNSTATUS), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKSSL), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPOP), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNOTLS), chkSsl.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_AUTOBODY), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACT), bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNICK), chkContact.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNOEVENT), chkContact.IsChecked() && bEnable); + } + +public: + CAccOptDlg() : + CBaseOptionsDlg(IDD_POP3ACCOUNTOPT), + cmbCP(this, IDC_COMBOCP), + btnAdd(this, IDC_BTNADD), + btnApp(this, IDC_BTNAPP), + btnDel(this, IDC_BTNDEL), + chkApp(this, IDC_CHECKAPP), + chkSsl(this, IDC_CHECKSSL), + btnReset(this, IDC_BTNRESET), + btnStatus(this, IDC_BTNSTATUS), + btnDefault(this, IDC_BTNDEFAULT), + cmbAccount(this, IDC_COMBOACCOUNT), + chkContact(this, IDC_CHECKCONTACT) + { + cmbCP.OnSelChanged = Callback(this, &CAccOptDlg::onSelChange_CP); + + cmbAccount.OnChange = Callback(this, &CAccOptDlg::onChange_Account); + cmbAccount.OnKillFocus = Callback(this, &CAccOptDlg::onKillFocus_Account); + cmbAccount.OnSelChanged = Callback(this, &CAccOptDlg::onSelChange_Account); + + chkApp.OnChange = Callback(this, &CAccOptDlg::onChangeApp); + chkSsl.OnChange = Callback(this, &CAccOptDlg::onChangeSsl); + chkContact.OnChange = Callback(this, &CAccOptDlg::onChangeContact); + + btnAdd.OnClick = Callback(this, &CAccOptDlg::onClick_Add); + btnDel.OnClick = Callback(this, &CAccOptDlg::onClick_Del); + btnApp.OnClick = Callback(this, &CAccOptDlg::onClick_App); + btnReset.OnClick = Callback(this, &CAccOptDlg::onClick_Reset); + btnStatus.OnClick = Callback(this, &CAccOptDlg::onClick_Status); + btnDefault.OnClick = Callback(this, &CAccOptDlg::onClick_Default); + } + + bool OnInitDialog() override + { + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); + + DlgEnableAccount(false); + DlgShowAccount(0); + + // Fill accounts + WaitToReadSO(POP3Plugin->AccountBrowserSO); + + for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) + if (ActualAccount->Name != nullptr) + cmbAccount.AddStringA(ActualAccount->Name); + cmbAccount.SetCurSel(0); + + ReadDoneSO(POP3Plugin->AccountBrowserSO); + + // Fill code pages + cmbCP.AddString(TranslateT("Default")); + for (int i = 1; i < CPLENSUPP; i++) { + CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[i].CP, 0, &info); + size_t len = mir_wstrlen(info.CodePageName + 7); + info.CodePageName[len + 6] = 0; + cmbCP.AddString(info.CodePageName + 7); + } + cmbCP.SetCurSel(0); + + ActualAccount = nullptr; + SendMessage(GetParent(m_hwnd), PSM_UNCHANGED, (WPARAM)m_hwnd, 0); + + WindowList_Add(pYAMNVar->MessageWnds, m_hwnd); + return true; + } + + void OnDestroy() override + { + WindowList_Remove(pYAMNVar->MessageWnds, m_hwnd); + } + + INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override + { + switch (msg) { + case WM_YAMN_CHANGESTATUS: + if ((HPOP3ACCOUNT)wParam == ActualAccount) { + wchar_t accstatus[256]; + GetAccountStatus(ActualAccount, accstatus); + SetDlgItemText(m_hwnd, IDC_STSTATUS, accstatus); + return TRUE; + } + break; + + case WM_YAMN_CHANGESTATUSOPTION: + NotifyChange(); + return TRUE; + + case WM_YAMN_CHANGETIME: + if ((HPOP3ACCOUNT)wParam == ActualAccount) { + wchar_t Text[256]; + mir_snwprintf(Text, TranslateT("Time left to next check [s]: %d"), (uint32_t)lParam); + SetDlgItemText(m_hwnd, IDC_STTIMELEFT, Text); + } + return TRUE; + } + return CDlgBase::DlgProc(msg, wParam, lParam); + } + + void onChange_Account(CCtrlCombo *) + { + ActualAccount = nullptr; + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + DlgEnableAccount(0 != GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput))); + } + + void onKillFocus_Account(CCtrlCombo *) + { + GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); + if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput))) { + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); + DlgEnableAccount(mir_strlen(DlgInput) > 0); + } + else { + DlgShowAccount(ActualAccount); + DlgEnableAccount(true); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); + } + } + + void onSelChange_Account(CCtrlCombo *) + { + if (CB_ERR != (Result = cmbAccount.GetCurSel())) + SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_GETLBTEXT, (WPARAM)Result, (LPARAM)DlgInput); + + if ((Result == CB_ERR) || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) { + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); + } + else { + DlgShowAccount(ActualAccount); + DlgEnableAccount(true); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); + } + } + + void onSelChange_CP(CCtrlCombo *) + { + int sel = cmbCP.GetCurSel(); + CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[sel].CP, 0, &info); + DlgSetItemTextW(m_hwnd, IDC_STSTATUS, info.CodePageName); + } + + void onChangeContact(CCtrlCheck *) + { + bool bEnabled = chkContact.IsChecked(); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNICK), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKCONTACTNOEVENT), bEnabled); + } + + void onChangeSsl(CCtrlCheck *) + { + bool bEnabled = chkSsl.IsChecked(); + SetDlgItemInt(m_hwnd, IDC_EDITPORT, bEnabled ? 995 : 110, FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKNOTLS), !bEnabled); + } + + void onChangeApp(CCtrlCheck *) + { + bool bEnabled = chkApp.IsChecked(); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNAPP), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPP), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITAPPPARAM), bEnabled); + } + + void onClick_Status(CCtrlButton *) + { + DialogBoxParamW(g_plugin.getInst(), MAKEINTRESOURCEW(IDD_CHOOSESTATUSMODES), m_hwnd, DlgProcPOP3AccStatusOpt, NULL); + } + + void onClick_Add(CCtrlButton *) + { + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + DlgShowAccount(0); + DlgEnableAccount(true); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); + DlgSetItemTextW(m_hwnd, IDC_EDITNAME, TranslateT("New Account")); + + int index = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_ADDSTRING, 0, (LPARAM)TranslateT("New Account")); + if (index != CB_ERR && index != CB_ERRSPACE) + SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_SETCURSEL, index, (LPARAM)TranslateT("New Account")); + } + + void onClick_App(CCtrlButton *) + { + wchar_t filter[MAX_PATH]; + mir_snwprintf(filter, L"%s (*.exe;*.bat;*.cmd;*.com)%c*.exe;*.bat;*.cmd;*.com%c%s (*.*)%c*.*%c", + TranslateT("Executables"), 0, 0, TranslateT("All Files"), 0, 0); + + OPENFILENAME OFNStruct = {0}; + OFNStruct.lStructSize = sizeof(OPENFILENAME); + OFNStruct.hwndOwner = m_hwnd; + OFNStruct.lpstrFilter = filter; + OFNStruct.nFilterIndex = 1; + OFNStruct.nMaxFile = MAX_PATH; + OFNStruct.lpstrFile = new wchar_t[MAX_PATH]; + OFNStruct.lpstrFile[0] = (wchar_t)0; + OFNStruct.lpstrTitle = TranslateT("Select executable used for notification"); + OFNStruct.Flags = OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR; + if (!GetOpenFileName(&OFNStruct)) { + if (CommDlgExtendedError()) + MessageBox(m_hwnd, TranslateT("Dialog box error"), TranslateT("Failed"), MB_OK); + } + else DlgSetItemTextW(m_hwnd, IDC_EDITAPP, OFNStruct.lpstrFile); + delete[] OFNStruct.lpstrFile; + } + + void onClick_Default(CCtrlButton *) + { + DlgShowAccount(0); + } + + void onClick_Del(CCtrlButton *) + { + GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), FALSE); + if ((CB_ERR == (Result = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0))) + || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) + return; + + if (IDOK != MessageBox(m_hwnd, TranslateT("Do you really want to delete this account?"), TranslateT("Delete account confirmation"), MB_OKCANCEL | MB_ICONWARNING)) + return; + + DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); + + if (ActualAccount->hContact != NULL) + db_delete_contact(ActualAccount->hContact); + + CallService(MS_YAMN_DELETEACCOUNT, (WPARAM)POP3Plugin, (LPARAM)ActualAccount); + + // We can consider our account as deleted. + SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_DELETESTRING, Result, 0); + DlgSetItemText(m_hwnd, IDC_COMBOACCOUNT, nullptr); + DlgEnableAccount(false); + DlgShowAccount(0); + } + + void onClick_Reset(CCtrlButton *) + { + if (ActualAccount != nullptr) + ActualAccount->TimeLeft = ActualAccount->Interval; + } + + bool OnApply() override + { + char Text[MAX_PATH]; + wchar_t TextW[MAX_PATH]; + BOOL Translated, NewAcc = FALSE; + size_t Length, index; + + if (!GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text))) + return false; + + BOOL Check = (IsDlgButtonChecked(m_hwnd, IDC_CHECK) == BST_CHECKED); + BOOL CheckSSL = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSSL) == BST_CHECKED); + BOOL CheckNoTLS = (IsDlgButtonChecked(m_hwnd, IDC_CHECKNOTLS) == BST_CHECKED); + BOOL CheckAPOP = (IsDlgButtonChecked(m_hwnd, IDC_CHECKAPOP) == BST_CHECKED); + + BOOL CheckABody = (IsDlgButtonChecked(m_hwnd, IDC_AUTOBODY) == BST_CHECKED); + BOOL CheckMsg = (IsDlgButtonChecked(m_hwnd, IDC_CHECKMSG) == BST_CHECKED); + BOOL CheckSnd = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSND) == BST_CHECKED); + BOOL CheckIco = (IsDlgButtonChecked(m_hwnd, IDC_CHECKICO) == BST_CHECKED); + + BOOL CheckApp = (IsDlgButtonChecked(m_hwnd, IDC_CHECKAPP) == BST_CHECKED); + BOOL CheckKBN = (IsDlgButtonChecked(m_hwnd, IDC_CHECKKBN) == BST_CHECKED); + BOOL CheckContact = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACT) == BST_CHECKED); + BOOL CheckContactNick = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACTNICK) == BST_CHECKED); + BOOL CheckContactNoEvent = (IsDlgButtonChecked(m_hwnd, IDC_CHECKCONTACTNOEVENT) == BST_CHECKED); + + BOOL CheckFSnd = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFSND) == BST_CHECKED); + BOOL CheckFMsg = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFMSG) == BST_CHECKED); + BOOL CheckFIco = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFICO) == BST_CHECKED); + + BOOL CheckNMsgP = (IsDlgButtonChecked(m_hwnd, IDC_CHECKNMSGP) == BST_CHECKED); + + UINT Port = GetDlgItemInt(m_hwnd, IDC_EDITPORT, &Translated, FALSE); + if (!Translated) { + MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); + SetFocus(GetDlgItem(m_hwnd, IDC_EDITPORT)); + return false; + } + + UINT Interval = GetDlgItemInt(m_hwnd, IDC_EDITINTERVAL, &Translated, FALSE); + if (!Translated) { + MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); + SetFocus(GetDlgItem(m_hwnd, IDC_EDITINTERVAL)); + return false; + } + + GetDlgItemTextA(m_hwnd, IDC_EDITAPP, Text, _countof(Text)); + if (CheckApp && !(Length = mir_strlen(Text))) { + MessageBox(m_hwnd, TranslateT("Please select application to run"), TranslateT("Input error"), MB_OK); + return false; + } + + GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text)); + if (!(Length = mir_strlen(Text))) { + GetDlgItemTextA(m_hwnd, IDC_EDITNAME, Text, _countof(Text)); + if (!(Length = mir_strlen(Text))) + return false; + } + + DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); + + if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)Text))) { + NewAcc = TRUE; + WaitToWriteSO(POP3Plugin->AccountBrowserSO); + if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_GETNEXTFREEACCOUNT, (WPARAM)POP3Plugin, (LPARAM)YAMN_ACCOUNTVERSION))) { + WriteDoneSO(POP3Plugin->AccountBrowserSO); + MessageBox(m_hwnd, TranslateT("Cannot allocate memory space for new account"), TranslateT("Memory error"), MB_OK); + return false; + } + DlgEnableAccount(true); + } + else { // We have to get full access to AccountBrowser, so other iterating thrads cannot get new account until new account is right set + WaitToWriteSO(POP3Plugin->AccountBrowserSO); + } + + if (WAIT_OBJECT_0 != WaitToWrite(ActualAccount)) + WriteDoneSO(POP3Plugin->AccountBrowserSO); + + GetDlgItemTextA(m_hwnd, IDC_EDITNAME, Text, _countof(Text)); + if (!(Length = mir_strlen(Text))) + return false; + if (nullptr != ActualAccount->Name) + delete[] ActualAccount->Name; + ActualAccount->Name = new char[mir_strlen(Text) + 1]; + mir_strcpy(ActualAccount->Name, Text); + + GetDlgItemTextA(m_hwnd, IDC_EDITSERVER, Text, _countof(Text)); + if (nullptr != ActualAccount->Server->Name) + delete[] ActualAccount->Server->Name; + ActualAccount->Server->Name = new char[mir_strlen(Text) + 1]; + mir_strcpy(ActualAccount->Server->Name, Text); + + GetDlgItemTextA(m_hwnd, IDC_EDITLOGIN, Text, _countof(Text)); + if (nullptr != ActualAccount->Server->Login) + delete[] ActualAccount->Server->Login; + ActualAccount->Server->Login = new char[mir_strlen(Text) + 1]; + mir_strcpy(ActualAccount->Server->Login, Text); + + GetDlgItemTextA(m_hwnd, IDC_EDITPASS, Text, _countof(Text)); + if (nullptr != ActualAccount->Server->Passwd) + delete[] ActualAccount->Server->Passwd; + ActualAccount->Server->Passwd = new char[mir_strlen(Text) + 1]; + mir_strcpy(ActualAccount->Server->Passwd, Text); + + GetDlgItemTextW(m_hwnd, IDC_EDITAPP, TextW, _countof(TextW)); + if (nullptr != ActualAccount->NewMailN.App) + delete[] ActualAccount->NewMailN.App; + ActualAccount->NewMailN.App = new wchar_t[mir_wstrlen(TextW) + 1]; + mir_wstrcpy(ActualAccount->NewMailN.App, TextW); + + GetDlgItemTextW(m_hwnd, IDC_EDITAPPPARAM, TextW, _countof(TextW)); + if (nullptr != ActualAccount->NewMailN.AppParam) + delete[] ActualAccount->NewMailN.AppParam; + ActualAccount->NewMailN.AppParam = new wchar_t[mir_wstrlen(TextW) + 1]; + mir_wstrcpy(ActualAccount->NewMailN.AppParam, TextW); + + ActualAccount->Server->Port = Port; + ActualAccount->Interval = Interval * 60; + + if (CB_ERR == (index = SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_GETCURSEL, 0, 0))) + index = CPDEFINDEX; + ActualAccount->CP = CodePageNamesSupp[index].CP; + + if (NewAcc) + ActualAccount->TimeLeft = Interval * 60; + + BOOL CheckStart = (IsDlgButtonChecked(m_hwnd, IDC_CHECKSTART) == BST_CHECKED); + BOOL CheckForce = (IsDlgButtonChecked(m_hwnd, IDC_CHECKFORCE) == BST_CHECKED); + + ActualAccount->Flags = + (Check ? YAMN_ACC_ENA : 0) | + (CheckSSL ? YAMN_ACC_SSL23 : 0) | + (CheckNoTLS ? YAMN_ACC_NOTLS : 0) | + (CheckAPOP ? YAMN_ACC_APOP : 0) | + (CheckABody ? YAMN_ACC_BODY : 0) | + (ActualAccount->Flags & YAMN_ACC_POPN); + + ActualAccount->StatusFlags &= 0xFFFF; + ActualAccount->StatusFlags |= + (CheckStart ? YAMN_ACC_STARTS : 0) | + (CheckForce ? YAMN_ACC_FORCE : 0); + + ActualAccount->NewMailN.Flags = + (CheckSnd ? YAMN_ACC_SND : 0) | + (CheckMsg ? YAMN_ACC_MSG : 0) | + (CheckIco ? YAMN_ACC_ICO : 0) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_POP) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_POPC) | + (CheckApp ? YAMN_ACC_APP : 0) | + (CheckKBN ? YAMN_ACC_KBN : 0) | + (CheckContact ? YAMN_ACC_CONT : 0) | + (CheckContactNick ? YAMN_ACC_CONTNICK : 0) | + (CheckContactNoEvent ? YAMN_ACC_CONTNOEVENT : 0) | + YAMN_ACC_MSGP; //this is default: when new mail arrives and window was displayed, leave it displayed. + + ActualAccount->NoNewMailN.Flags = + (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POP) | + (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC) | + (CheckNMsgP ? YAMN_ACC_MSGP : 0); + + ActualAccount->BadConnectN.Flags = + (CheckFSnd ? YAMN_ACC_SND : 0) | + (CheckFMsg ? YAMN_ACC_MSG : 0) | + (CheckFIco ? YAMN_ACC_ICO : 0) | + (ActualAccount->BadConnectN.Flags & YAMN_ACC_POP) | + (ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC); + + WriteDone(ActualAccount); + WriteDoneSO(POP3Plugin->AccountBrowserSO); + + EnableWindow(GetDlgItem(m_hwnd, IDC_BTNDEL), TRUE); + + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + + index = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0); + + HPOP3ACCOUNT temp = ActualAccount; + + SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_RESETCONTENT, 0, 0); + if (POP3Plugin->FirstAccount != nullptr) + for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) + if (ActualAccount->Name != nullptr) + SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_ADDSTRING, 0, (LPARAM)ActualAccount->Name); + + ActualAccount = temp; + SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_SETCURSEL, (WPARAM)index, (LPARAM)ActualAccount->Name); + + WritePOP3Accounts(); + RefreshContact(); + return TRUE; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// Popup options + +class CPopupOptsDlg : public CBaseOptionsDlg +{ + HPOP3ACCOUNT ActualAccount = nullptr; + UCHAR ActualStatus; + + CCtrlCombo cmbAccount, cmbCP; + CCtrlCheck chkCol, chkFcol, chkNcol, chkPop, chkFpop, chkNpop; + CCtrlButton btnPreview; + + void DlgShowAccountPopup() + { + if (ActualAccount) { + WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread + SetDlgItemInt(m_hwnd, IDC_EDITPOPS, ActualAccount->NewMailN.PopupTime, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, ActualAccount->NoNewMailN.PopupTime, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, ActualAccount->BadConnectN.PopupTime, FALSE); + + chkPop.SetState(ActualAccount->NewMailN.Flags & YAMN_ACC_POP); + chkCol.SetState(ActualAccount->NewMailN.Flags & YAMN_ACC_POPC); + chkNpop.SetState(ActualAccount->NoNewMailN.Flags & YAMN_ACC_POP); + chkNcol.SetState(ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC); + chkFpop.SetState(ActualAccount->BadConnectN.Flags & YAMN_ACC_POP ? BST_CHECKED : BST_UNCHECKED); + chkFcol.SetState(ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC); + CheckDlgButton(m_hwnd, IDC_RADIOPOPN, ActualAccount->Flags & YAMN_ACC_POPN ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOP1, ActualAccount->Flags & YAMN_ACC_POPN ? BST_UNCHECKED : BST_CHECKED); + ReadDone(ActualAccount); + } + else { // default + SetDlgItemInt(m_hwnd, IDC_EDITPOPS, 0, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITNPOPS, 0, FALSE); + SetDlgItemInt(m_hwnd, IDC_EDITFPOPS, 0, FALSE); + chkPop.SetState(true); + chkCol.SetState(true); + chkNpop.SetState(true); + chkNcol.SetState(true); + chkFpop.SetState(true); + chkFcol.SetState(true); + CheckDlgButton(m_hwnd, IDC_RADIOPOPN, BST_UNCHECKED); + CheckDlgButton(m_hwnd, IDC_RADIOPOP1, BST_CHECKED); + } + } + + void DlgEnableAccountPopup(bool bEnable) + { + chkPop.Enable(bEnable); + chkCol.Enable(chkPop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPOPS), chkPop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && chkPop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && chkPop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOPN), chkPop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOP1), chkPop.IsChecked() && bEnable); + + chkNpop.Enable(bEnable); + chkNcol.Enable(chkNpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNPOPS), chkNpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && chkNpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && chkNpop.IsChecked() && bEnable); + + chkFpop.Enable(bEnable); + chkFcol.Enable(chkFpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITFPOPS), chkFpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && chkFpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && chkFpop.IsChecked() && bEnable); + EnableWindow(GetDlgItem(m_hwnd, IDC_CHECKAPOP), bEnable); + } + + void DlgShowAccountColors() + { + WaitToRead(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread + + if (ActualAccount->NewMailN.Flags & YAMN_ACC_POPC) { + SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NewMailN.PopupB); + SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NewMailN.PopupT); + } + else { + SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); + SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); + } + if (ActualAccount->BadConnectN.Flags & YAMN_ACC_POPC) { + SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->BadConnectN.PopupB); + SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->BadConnectN.PopupT); + } + else { + SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); + SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); + } + if (ActualAccount->NoNewMailN.Flags & YAMN_ACC_POPC) { + SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NoNewMailN.PopupB); + SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_SETCOLOUR, 0, (LPARAM)ActualAccount->NoNewMailN.PopupT); + } + else { + SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); + SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_SETCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOWTEXT)); + } + + ReadDone(ActualAccount); //we do not need to check if account is deleted. It is not deleted, because only thread that can delete account is this thread + } + +public: + CPopupOptsDlg() : + CBaseOptionsDlg(IDD_POP3ACCOUNTPOPUP), + cmbCP(this, IDC_COMBOCP), + chkCol(this, IDC_CHECKCOL), + chkPop(this, IDC_CHECKPOP), + chkFcol(this, IDC_CHECKFCOL), + chkFpop(this, IDC_CHECKFPOP), + chkNcol(this, IDC_CHECKNCOL), + chkNpop(this, IDC_CHECKNPOP), + btnPreview(this, IDC_PREVIEW), + cmbAccount(this, IDC_COMBOACCOUNT) + { + chkPop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Pop); + chkFpop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Fpop); + chkNpop.OnChange = Callback(this, &CPopupOptsDlg::onChange_Npop); + chkCol.OnChange = chkFcol.OnChange = chkNcol.OnChange = Callback(this, &CPopupOptsDlg::onChange_Col); + + cmbCP.OnSelChanged = Callback(this, &CPopupOptsDlg::onSelChange_CP); + + btnPreview.OnClick = Callback(this, &CPopupOptsDlg::onClick_Preview); + + cmbAccount.OnKillFocus = Callback(this, &CPopupOptsDlg::onKillFocus_Account); + cmbAccount.OnSelChanged = Callback(this, &CPopupOptsDlg::onSelChange_Account); + } + + bool OnInitDialog() override + { + WindowList_Add(pYAMNVar->MessageWnds, m_hwnd); + + DlgEnableAccountPopup(false); + DlgShowAccountPopup(); + + WaitToReadSO(POP3Plugin->AccountBrowserSO); + + if (POP3Plugin->FirstAccount != nullptr) + for (ActualAccount = (HPOP3ACCOUNT)POP3Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = (HPOP3ACCOUNT)ActualAccount->Next) + if (ActualAccount->Name != nullptr) + cmbAccount.AddStringA(ActualAccount->Name); + + ReadDoneSO(POP3Plugin->AccountBrowserSO); + ActualAccount = nullptr; + cmbAccount.SetCurSel(0); + return true; + } + + void OnDestroy() override + { + WindowList_Remove(pYAMNVar->MessageWnds, m_hwnd); + } + + void onKillFocus_Account(CCtrlCombo *) + { + GetDlgItemTextA(m_hwnd, IDC_COMBOACCOUNT, DlgInput, _countof(DlgInput)); + if (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput))) { + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + if (mir_strlen(DlgInput)) + DlgEnableAccountPopup(true); + else + DlgEnableAccountPopup(false); + } + else { + DlgShowAccount(ActualAccount); + DlgShowAccountColors(); + DlgEnableAccountPopup(true); + } + } + + void onSelChange_Account(CCtrlCombo *) + { + int Result = SendDlgItemMessage(m_hwnd, IDC_COMBOACCOUNT, CB_GETCURSEL, 0, 0); + if (CB_ERR != Result) + SendDlgItemMessageA(m_hwnd, IDC_COMBOACCOUNT, CB_GETLBTEXT, (WPARAM)Result, (LPARAM)DlgInput); + if ((Result == CB_ERR) || (nullptr == (ActualAccount = (HPOP3ACCOUNT)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)DlgInput)))) { + DlgSetItemText(m_hwnd, (WPARAM)IDC_STTIMELEFT, nullptr); + } + else { + DlgShowAccount(ActualAccount); + DlgShowAccountColors(); + DlgEnableAccountPopup(true); + } + } + + void onSelChange_CP(CCtrlCombo *) + { + int sel = SendDlgItemMessage(m_hwnd, IDC_COMBOCP, CB_GETCURSEL, 0, 0); + CPINFOEX info; GetCPInfoEx(CodePageNamesSupp[sel].CP, 0, &info); + DlgSetItemTextW(m_hwnd, IDC_STSTATUS, info.CodePageName); + } + + void onChange_Col(CCtrlCheck *) + { + EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && chkPop.IsChecked()); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && chkPop.IsChecked()); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && chkNpop.IsChecked()); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && chkNpop.IsChecked()); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && chkFpop.IsChecked()); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && chkFpop.IsChecked()); + } + + void onClick_Preview(CCtrlButton *) + { + if (chkPop.IsChecked()) { + POPUPDATAW Tester = {}; + Tester.lchIcon = g_plugin.getIcon(IDI_NEWMAIL); + mir_wstrncpy(Tester.lpwzContactName, TranslateT("Account Test"), MAX_CONTACTNAME); + mir_wstrncpy(Tester.lpwzText, TranslateT("You have N new mail messages"), MAX_SECONDLINE); + if (chkCol.IsChecked()) { + Tester.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_GETCOLOUR, 0, 0); + Tester.colorText = SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_GETCOLOUR, 0, 0); + } + else { + Tester.colorBack = GetSysColor(COLOR_BTNFACE); + Tester.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + PUAddPopupW(&Tester); + } + + if (chkFpop.IsChecked()) { + POPUPDATAW TesterF = {}; + TesterF.lchIcon = g_plugin.getIcon(IDI_BADCONNECT); + mir_wstrncpy(TesterF.lpwzContactName, TranslateT("Account Test (failed)"), MAX_CONTACTNAME); + mir_wstrncpy(TesterF.lpwzText, TranslateT("Connection failed message"), MAX_SECONDLINE); + if (chkFcol.IsChecked()) { + TesterF.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_GETCOLOUR, 0, 0); + TesterF.colorText = SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_GETCOLOUR, 0, 0); + } + else { + TesterF.colorBack = GetSysColor(COLOR_BTNFACE); + TesterF.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + PUAddPopupW(&TesterF); + } + + if (chkNpop.IsChecked()) { + POPUPDATAW TesterN = {}; + TesterN.lchIcon = g_plugin.getIcon(IDI_LAUNCHAPP); + mir_wstrncpy(TesterN.lpwzContactName, TranslateT("Account Test"), MAX_CONTACTNAME); + mir_wstrncpy(TesterN.lpwzText, TranslateT("No new mail message"), MAX_SECONDLINE); + if (chkNcol.IsChecked()) { + TesterN.colorBack = SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_GETCOLOUR, 0, 0); + TesterN.colorText = SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_GETCOLOUR, 0, 0); + } + else { + TesterN.colorBack = GetSysColor(COLOR_BTNFACE); + TesterN.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + PUAddPopupW(&TesterN); + } + } + + void onChange_Pop(CCtrlCheck *) + { + bool bEnabled = chkPop.IsChecked(); + chkCol.Enable(bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPB), chkCol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPT), chkCol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOPN), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_RADIOPOP1), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITPOPS), bEnabled); + } + + void onChange_Fpop(CCtrlCheck *) + { + bool bEnabled = chkFpop.IsChecked(); + chkFcol.Enable(bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFB), chkFcol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPFT), chkFcol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITFPOPS), bEnabled); + } + + void onChange_Npop(CCtrlCheck *) + { + bool bEnabled = chkNpop.IsChecked(); + chkNcol.Enable(bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNB), chkNcol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_CPNT), chkNcol.IsChecked() && bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_EDITNPOPS), bEnabled); + } + + bool OnApply() override + { + wchar_t Text[MAX_PATH]; + if (!GetDlgItemText(m_hwnd, IDC_COMBOACCOUNT, Text, _countof(Text))) + return false; + + BOOL CheckPopup = chkPop.IsChecked(); + BOOL CheckPopupW = chkCol.IsChecked(); + + BOOL CheckFPopup = chkFpop.IsChecked(); + BOOL CheckFPopupW = chkFcol.IsChecked(); + + BOOL CheckNPopup = chkNpop.IsChecked(); + BOOL CheckNPopupW = chkNcol.IsChecked(); + + BOOL CheckPopN = (IsDlgButtonChecked(m_hwnd, IDC_RADIOPOPN) == BST_CHECKED); + + BOOL Translated; + UINT Time = GetDlgItemInt(m_hwnd, IDC_EDITPOPS, &Translated, FALSE); + if (!Translated) { + MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); + SetFocus(GetDlgItem(m_hwnd, IDC_EDITPOPS)); + return false; + } + UINT TimeN = GetDlgItemInt(m_hwnd, IDC_EDITNPOPS, &Translated, FALSE); + if (!Translated) { + MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); + SetFocus(GetDlgItem(m_hwnd, IDC_EDITNPOPS)); + return false; + } + UINT TimeF = GetDlgItemInt(m_hwnd, IDC_EDITFPOPS, &Translated, FALSE); + if (!Translated) { + MessageBox(m_hwnd, TranslateT("This is not a valid number value"), TranslateT("Input error"), MB_OK); + SetFocus(GetDlgItem(m_hwnd, IDC_EDITFPOPS)); + return false; + } + + DlgSetItemTextW(m_hwnd, IDC_STTIMELEFT, TranslateT("Please wait while no account is in use.")); + + ActualAccount->Flags = + (ActualAccount->Flags & YAMN_ACC_ENA) | + (ActualAccount->Flags & YAMN_ACC_SSL23) | + (ActualAccount->Flags & YAMN_ACC_NOTLS) | + (ActualAccount->Flags & YAMN_ACC_APOP) | + (ActualAccount->Flags & YAMN_ACC_BODY) | + (CheckPopN ? YAMN_ACC_POPN : 0); + + ActualAccount->NewMailN.Flags = + (ActualAccount->NewMailN.Flags & YAMN_ACC_SND) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_MSG) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_ICO) | + (CheckPopup ? YAMN_ACC_POP : 0) | + (CheckPopupW ? YAMN_ACC_POPC : 0) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_APP) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_KBN) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_CONT) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_CONTNICK) | + (ActualAccount->NewMailN.Flags & YAMN_ACC_CONTNOEVENT) | + YAMN_ACC_MSGP; + + ActualAccount->NoNewMailN.Flags = + (CheckNPopup ? YAMN_ACC_POP : 0) | + (CheckNPopupW ? YAMN_ACC_POPC : 0) | + (ActualAccount->NoNewMailN.Flags & YAMN_ACC_MSGP); + + ActualAccount->BadConnectN.Flags = + (ActualAccount->BadConnectN.Flags & YAMN_ACC_SND) | + (ActualAccount->BadConnectN.Flags & YAMN_ACC_MSG) | + (ActualAccount->BadConnectN.Flags & YAMN_ACC_ICO) | + (CheckFPopup ? YAMN_ACC_POP : 0) | + (CheckFPopupW ? YAMN_ACC_POPC : 0); + + ActualAccount->NewMailN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPB, CPM_GETCOLOUR, 0, 0); + ActualAccount->NewMailN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPT, CPM_GETCOLOUR, 0, 0); + ActualAccount->NewMailN.PopupTime = Time; + + ActualAccount->NoNewMailN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPNB, CPM_GETCOLOUR, 0, 0); + ActualAccount->NoNewMailN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPNT, CPM_GETCOLOUR, 0, 0); + ActualAccount->NoNewMailN.PopupTime = TimeN; + + ActualAccount->BadConnectN.PopupB = SendDlgItemMessage(m_hwnd, IDC_CPFB, CPM_GETCOLOUR, 0, 0); + ActualAccount->BadConnectN.PopupT = SendDlgItemMessage(m_hwnd, IDC_CPFT, CPM_GETCOLOUR, 0, 0); + ActualAccount->BadConnectN.PopupTime = TimeF; + + WriteDone(ActualAccount); + WriteDoneSO(POP3Plugin->AccountBrowserSO); + + WritePOP3Accounts(); + RefreshContact(); + return TRUE; + } +}; + +//-------------------------------------------------------------------------------------------------- + +int YAMNOptInitSvc(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.szGroup.a = LPGEN("Network"); + odp.szTitle.a = LPGEN("YAMN"); + odp.flags = ODPF_BOLDGROUPS; + + odp.szTab.a = LPGEN("Accounts"); + odp.pDialog = new CAccOptDlg(); + g_plugin.addOptions(wParam, &odp); + + odp.szTab.a = LPGEN("General"); + odp.pDialog = new CGeneralOptDlg(); + g_plugin.addOptions(wParam, &odp); + + odp.szGroup.a = LPGEN("Popups"); + odp.szTab.a = LPGEN("YAMN"); + odp.pDialog = new CPopupOptsDlg(); + g_plugin.addOptions(wParam, &odp); + return 0; +} diff --git a/protocols/YAMN/src/protoplugin.cpp b/protocols/YAMN/src/protoplugin.cpp index c6e6a6aecc..b192ac1f5f 100644 --- a/protocols/YAMN/src/protoplugin.cpp +++ b/protocols/YAMN/src/protoplugin.cpp @@ -1,172 +1,172 @@ -/* - * YAMN plugin export functions for protocols - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - - //-------------------------------------------------------------------------------------------------- - -PYAMN_PROTOPLUGINQUEUE FirstProtoPlugin = nullptr; - -INT_PTR RegisterProtocolPluginSvc(WPARAM, LPARAM); - -//Removes plugin from queue and deletes registration structures -INT_PTR UnregisterProtocolPlugin(HYAMNPROTOPLUGIN Plugin); - -INT_PTR UnregisterProtocolPluginSvc(WPARAM, LPARAM); - -//Removes plugins from queue and deletes registration structures -INT_PTR UnregisterProtoPlugins(); - -//Sets imported functions for an plugin and therefore it starts plugin to be registered and running -// Plugin- plugin, which wants to set its functions -// YAMNFcn- pointer to imported functions with accounts -// YAMNFcnVer- version of YAMN_PROTOIMPORTFCN, use YAMN_PROTOIMPORTFCNVERSION -// YAMNMailFcn- pointer to imported functions with mails -// YAMNMailFcnVer- version of YAMN_MAILIMPORTFCN, use YAMN_MAILIMPORTFCNVERSION -// returns nonzero if success -int WINAPI SetProtocolPluginFcnImportFcn(HYAMNPROTOPLUGIN Plugin, PYAMN_PROTOIMPORTFCN YAMNFcn, uint32_t YAMNFcnVer, PYAMN_MAILIMPORTFCN YAMNMailFcn, uint32_t YAMNMailFcnVer); - -struct CExportedFunctions ProtoPluginExportedFcn[] = -{ - {YAMN_SETPROTOCOLPLUGINFCNIMPORTID, (void *)SetProtocolPluginFcnImportFcn}, -}; - -struct CExportedServices ProtoPluginExportedSvc[] = -{ - {MS_YAMN_REGISTERPROTOPLUGIN, RegisterProtocolPluginSvc}, - {MS_YAMN_UNREGISTERPROTOPLUGIN, UnregisterProtocolPluginSvc}, - {MS_YAMN_GETFILENAME, GetFileNameSvc}, - {MS_YAMN_DELETEFILENAME, DeleteFileNameSvc}, -}; - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -INT_PTR RegisterProtocolPluginSvc(WPARAM wParam, LPARAM lParam) -{ - PYAMN_PROTOREGISTRATION Registration = (PYAMN_PROTOREGISTRATION)wParam; - HYAMNPROTOPLUGIN Plugin; - - if (lParam != YAMN_PROTOREGISTRATIONVERSION) - return 0; - if ((Registration->Name == nullptr) || (Registration->Ver == nullptr)) - return (INT_PTR)NULL; - if (nullptr == (Plugin = new YAMN_PROTOPLUGIN)) - return (INT_PTR)NULL; - - Plugin->PluginInfo = Registration; - - Plugin->FirstAccount = nullptr; - - Plugin->AccountBrowserSO = new SWMRG; - SWMRGInitialize(Plugin->AccountBrowserSO, nullptr); - - Plugin->Fcn = nullptr; - Plugin->MailFcn = nullptr; - - return (INT_PTR)Plugin; -} - -int WINAPI SetProtocolPluginFcnImportFcn(HYAMNPROTOPLUGIN Plugin, PYAMN_PROTOIMPORTFCN YAMNFcn, uint32_t YAMNFcnVer, PYAMN_MAILIMPORTFCN YAMNMailFcn, uint32_t YAMNMailFcnVer) -{ - PYAMN_PROTOPLUGINQUEUE Parser; - - if (YAMNFcnVer != YAMN_PROTOIMPORTFCNVERSION) - return 0; - if (YAMNMailFcnVer != YAMN_MAILIMPORTFCNVERSION) - return 0; - if (YAMNFcn == nullptr) - return 0; - if (YAMNMailFcn == nullptr) - return 0; - - Plugin->Fcn = YAMNFcn; - Plugin->MailFcn = YAMNMailFcn; - - mir_cslock lck(PluginRegCS); - // We add protocol to the protocol list - for (Parser = FirstProtoPlugin; Parser != nullptr && Parser->Next != nullptr; Parser = Parser->Next); - if (Parser == nullptr) { - FirstProtoPlugin = new YAMN_PROTOPLUGINQUEUE; - Parser = FirstProtoPlugin; - } - else { - Parser->Next = new YAMN_PROTOPLUGINQUEUE; - Parser = Parser->Next; - } - - Parser->Plugin = Plugin; - Parser->Next = nullptr; - return 1; -} - -INT_PTR UnregisterProtocolPlugin(HYAMNPROTOPLUGIN Plugin) -{ - PYAMN_PROTOPLUGINQUEUE Parser, Found; - - if (FirstProtoPlugin->Plugin == Plugin) { - Found = FirstProtoPlugin; - FirstProtoPlugin = FirstProtoPlugin->Next; - } - else { - for (Parser = FirstProtoPlugin; (Parser->Next != nullptr) && (Plugin != Parser->Next->Plugin); Parser = Parser->Next); - if (Parser->Next != nullptr) { - Found = Parser->Next; - Parser->Next = Parser->Next->Next; - } - else - Found = nullptr; - } - if (Found != nullptr) { - StopAccounts(Plugin); - DeleteAccounts(Plugin); - if (Plugin->Fcn->UnLoadFcn != nullptr) - Plugin->Fcn->UnLoadFcn((void *)nullptr); - - delete Found->Plugin->AccountBrowserSO; - delete Found->Plugin; - delete Found; - } - else - return 0; - return 1; -} - -INT_PTR UnregisterProtocolPluginSvc(WPARAM wParam, LPARAM) -{ - HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; - - mir_cslock lck(PluginRegCS); - UnregisterProtocolPlugin(Plugin); - return 1; -} - -INT_PTR UnregisterProtoPlugins() -{ - mir_cslock lck(PluginRegCS); - // We remove protocols from the protocol list - while (FirstProtoPlugin != nullptr) - UnregisterProtocolPlugin(FirstProtoPlugin->Plugin); - return 1; -} - -INT_PTR GetFileNameSvc(WPARAM wParam, LPARAM) -{ - wchar_t *FileName = new wchar_t[MAX_PATH]; - if (FileName == nullptr) - return NULL; - - mir_snwprintf(FileName, MAX_PATH, L"%s\\yamn-accounts.%s.%s.book", UserDirectory, (wchar_t *)wParam, ProfileName); - return (INT_PTR)FileName; -} - -INT_PTR DeleteFileNameSvc(WPARAM wParam, LPARAM) -{ - if ((wchar_t *)wParam != nullptr) - delete[](wchar_t *) wParam; - - return 0; -} +/* + * YAMN plugin export functions for protocols + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + + //-------------------------------------------------------------------------------------------------- + +PYAMN_PROTOPLUGINQUEUE FirstProtoPlugin = nullptr; + +INT_PTR RegisterProtocolPluginSvc(WPARAM, LPARAM); + +//Removes plugin from queue and deletes registration structures +INT_PTR UnregisterProtocolPlugin(HYAMNPROTOPLUGIN Plugin); + +INT_PTR UnregisterProtocolPluginSvc(WPARAM, LPARAM); + +//Removes plugins from queue and deletes registration structures +INT_PTR UnregisterProtoPlugins(); + +//Sets imported functions for an plugin and therefore it starts plugin to be registered and running +// Plugin- plugin, which wants to set its functions +// YAMNFcn- pointer to imported functions with accounts +// YAMNFcnVer- version of YAMN_PROTOIMPORTFCN, use YAMN_PROTOIMPORTFCNVERSION +// YAMNMailFcn- pointer to imported functions with mails +// YAMNMailFcnVer- version of YAMN_MAILIMPORTFCN, use YAMN_MAILIMPORTFCNVERSION +// returns nonzero if success +int WINAPI SetProtocolPluginFcnImportFcn(HYAMNPROTOPLUGIN Plugin, PYAMN_PROTOIMPORTFCN YAMNFcn, uint32_t YAMNFcnVer, PYAMN_MAILIMPORTFCN YAMNMailFcn, uint32_t YAMNMailFcnVer); + +struct CExportedFunctions ProtoPluginExportedFcn[] = +{ + {YAMN_SETPROTOCOLPLUGINFCNIMPORTID, (void *)SetProtocolPluginFcnImportFcn}, +}; + +struct CExportedServices ProtoPluginExportedSvc[] = +{ + {MS_YAMN_REGISTERPROTOPLUGIN, RegisterProtocolPluginSvc}, + {MS_YAMN_UNREGISTERPROTOPLUGIN, UnregisterProtocolPluginSvc}, + {MS_YAMN_GETFILENAME, GetFileNameSvc}, + {MS_YAMN_DELETEFILENAME, DeleteFileNameSvc}, +}; + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +INT_PTR RegisterProtocolPluginSvc(WPARAM wParam, LPARAM lParam) +{ + PYAMN_PROTOREGISTRATION Registration = (PYAMN_PROTOREGISTRATION)wParam; + HYAMNPROTOPLUGIN Plugin; + + if (lParam != YAMN_PROTOREGISTRATIONVERSION) + return 0; + if ((Registration->Name == nullptr) || (Registration->Ver == nullptr)) + return (INT_PTR)NULL; + if (nullptr == (Plugin = new YAMN_PROTOPLUGIN)) + return (INT_PTR)NULL; + + Plugin->PluginInfo = Registration; + + Plugin->FirstAccount = nullptr; + + Plugin->AccountBrowserSO = new SWMRG; + SWMRGInitialize(Plugin->AccountBrowserSO, nullptr); + + Plugin->Fcn = nullptr; + Plugin->MailFcn = nullptr; + + return (INT_PTR)Plugin; +} + +int WINAPI SetProtocolPluginFcnImportFcn(HYAMNPROTOPLUGIN Plugin, PYAMN_PROTOIMPORTFCN YAMNFcn, uint32_t YAMNFcnVer, PYAMN_MAILIMPORTFCN YAMNMailFcn, uint32_t YAMNMailFcnVer) +{ + PYAMN_PROTOPLUGINQUEUE Parser; + + if (YAMNFcnVer != YAMN_PROTOIMPORTFCNVERSION) + return 0; + if (YAMNMailFcnVer != YAMN_MAILIMPORTFCNVERSION) + return 0; + if (YAMNFcn == nullptr) + return 0; + if (YAMNMailFcn == nullptr) + return 0; + + Plugin->Fcn = YAMNFcn; + Plugin->MailFcn = YAMNMailFcn; + + mir_cslock lck(PluginRegCS); + // We add protocol to the protocol list + for (Parser = FirstProtoPlugin; Parser != nullptr && Parser->Next != nullptr; Parser = Parser->Next); + if (Parser == nullptr) { + FirstProtoPlugin = new YAMN_PROTOPLUGINQUEUE; + Parser = FirstProtoPlugin; + } + else { + Parser->Next = new YAMN_PROTOPLUGINQUEUE; + Parser = Parser->Next; + } + + Parser->Plugin = Plugin; + Parser->Next = nullptr; + return 1; +} + +INT_PTR UnregisterProtocolPlugin(HYAMNPROTOPLUGIN Plugin) +{ + PYAMN_PROTOPLUGINQUEUE Parser, Found; + + if (FirstProtoPlugin->Plugin == Plugin) { + Found = FirstProtoPlugin; + FirstProtoPlugin = FirstProtoPlugin->Next; + } + else { + for (Parser = FirstProtoPlugin; (Parser->Next != nullptr) && (Plugin != Parser->Next->Plugin); Parser = Parser->Next); + if (Parser->Next != nullptr) { + Found = Parser->Next; + Parser->Next = Parser->Next->Next; + } + else + Found = nullptr; + } + if (Found != nullptr) { + StopAccounts(Plugin); + DeleteAccounts(Plugin); + if (Plugin->Fcn->UnLoadFcn != nullptr) + Plugin->Fcn->UnLoadFcn((void *)nullptr); + + delete Found->Plugin->AccountBrowserSO; + delete Found->Plugin; + delete Found; + } + else + return 0; + return 1; +} + +INT_PTR UnregisterProtocolPluginSvc(WPARAM wParam, LPARAM) +{ + HYAMNPROTOPLUGIN Plugin = (HYAMNPROTOPLUGIN)wParam; + + mir_cslock lck(PluginRegCS); + UnregisterProtocolPlugin(Plugin); + return 1; +} + +INT_PTR UnregisterProtoPlugins() +{ + mir_cslock lck(PluginRegCS); + // We remove protocols from the protocol list + while (FirstProtoPlugin != nullptr) + UnregisterProtocolPlugin(FirstProtoPlugin->Plugin); + return 1; +} + +INT_PTR GetFileNameSvc(WPARAM wParam, LPARAM) +{ + wchar_t *FileName = new wchar_t[MAX_PATH]; + if (FileName == nullptr) + return NULL; + + mir_snwprintf(FileName, MAX_PATH, L"%s\\yamn-accounts.%s.%s.book", UserDirectory, (wchar_t *)wParam, ProfileName); + return (INT_PTR)FileName; +} + +INT_PTR DeleteFileNameSvc(WPARAM wParam, LPARAM) +{ + if ((wchar_t *)wParam != nullptr) + delete[](wchar_t *) wParam; + + return 0; +} diff --git a/protocols/YAMN/src/resource.h b/protocols/YAMN/src/resource.h index 3f6a52fa55..70cb2647c8 100644 --- a/protocols/YAMN/src/resource.h +++ b/protocols/YAMN/src/resource.h @@ -1,111 +1,111 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by W:\miranda-ng\protocols\YAMN\res\YAMN.rc -// -#define IDI_CHECKMAIL 104 -#define IDI_LAUNCHAPP 105 -#define IDD_DLGVIEWMESSAGES 107 -#define IDD_DLGSHOWMESSAGE 108 -#define IDI_ICOYAMN2 112 -#define IDI_ICOYAMN1 113 -#define IDD_DLGBADCONNECT 115 -#define IDD_POP3ACCOUNTOPT 121 -#define IDD_YAMNOPT 126 -#define IDI_BADCONNECT 131 -#define IDI_ICOTTBUP 138 -#define IDI_NEWMAIL 159 -#define IDD_CHOOSESTATUSMODES 310 -#define IDD_OPTIONS 311 -#define IDD_POP3ACCOUNTPOPUP 312 -#define IDC_EDITSERVER 1000 -#define IDC_EDITPORT 1001 -#define IDC_EDITLOGIN 1002 -#define IDC_EDITPASS 1003 -#define IDC_COMBOACCOUNT 1005 -#define IDC_BTNDEFAULT 1006 -#define IDC_EDITINTERVAL 1007 -#define IDC_CHECKSND 1008 -#define IDC_CHECKMSG 1009 -#define IDC_CHECKAPP 1010 -#define IDC_BTNAPP 1011 -#define IDC_CHECKICO 1012 -#define IDC_CHECK 1013 -#define IDC_BTNDEL 1014 -#define IDC_CHECKFSND 1016 -#define IDC_CHECKFMSG 1017 -#define IDC_CHECKFICO 1018 -#define IDC_CHECKST0 1019 -#define IDC_CHECKST1 1020 -#define IDC_CHECKST2 1021 -#define IDC_CHECKST3 1022 -#define IDC_CHECKST4 1023 -#define IDC_CHECKST5 1024 -#define IDC_CHECKST6 1025 -#define IDC_CHECKST7 1026 -#define IDC_EDITAPP 1027 -#define IDC_CHECKCONTACT 1030 -#define IDC_CHECKCONTACTNICK 1031 -#define IDC_CHECKCONTACTNOEVENT 1032 -#define IDC_STTIMELEFT 1033 -#define IDC_LISTMAILS 1038 -#define IDC_LISTHEADERS 1039 -#define IDC_EDITAPPPARAM 1044 -#define IDC_BTNOK 1047 -#define IDC_COMBOCP 1050 -#define IDC_STATICMSG 1055 -#define IDC_AUTOBODY 1062 -#define IDC_BTNRESET 1063 -#define IDC_CHECKSTART 1064 -#define IDC_STWCHECK 1065 -#define IDC_CHECKFORCE 1066 -#define IDC_RADIOPOP1 1068 -#define IDC_RADIOPOPN 1069 -#define IDC_CPB 1070 -#define IDC_CPNB 1071 -#define IDC_CHECKCOL 1073 -#define IDC_CPT 1074 -#define IDC_CPFB 1075 -#define IDC_CPFT 1076 -#define IDC_CHECKFCOL 1077 -#define IDC_CHECKNCOL 1078 -#define IDC_CPNT 1079 -#define IDC_CHECKPOP 1087 -#define IDC_CHECKNPOP 1088 -#define IDC_CHECKFPOP 1089 -#define IDC_EDITPOPS 1090 -#define IDC_EDITNPOPS 1091 -#define IDC_EDITFPOPS 1092 -#define IDC_GBNEWMAIL 1094 -#define IDC_GBNONEWMAIL 1095 -#define IDC_GBBADCONNECT 1096 -#define IDC_STSTATUS 1102 -#define IDC_CHECKTTB 1117 -#define IDC_CHECKSSL 1117 -#define IDC_CHECKNMSGP 1118 -#define IDC_CHECKNOTLS 1120 -#define IDC_CHECKKBN 1121 -#define IDC_BTNSTATUS 1123 -#define IDC_OPTIONSTAB 1124 -#define IDC_BTNCHECKALL 1125 -#define IDC_CLOSEONDELETE 1127 -#define IDC_LONGDATE 1128 -#define IDC_SMARTDATE 1129 -#define IDC_NOSECONDS 1130 -#define IDC_CHECKAPOP 1200 -#define IDC_STATUSGROUP 1338 -#define IDC_SPLITTER 1400 -#define IDC_EDITBODY 1401 -#define IDC_PREVIEW 1402 -#define IDC_BTNADD 1403 -#define IDC_EDITNAME 1404 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 144 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1407 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by W:\miranda-ng\protocols\YAMN\res\YAMN.rc +// +#define IDI_CHECKMAIL 104 +#define IDI_LAUNCHAPP 105 +#define IDD_DLGVIEWMESSAGES 107 +#define IDD_DLGSHOWMESSAGE 108 +#define IDI_ICOYAMN2 112 +#define IDI_ICOYAMN1 113 +#define IDD_DLGBADCONNECT 115 +#define IDD_POP3ACCOUNTOPT 121 +#define IDD_YAMNOPT 126 +#define IDI_BADCONNECT 131 +#define IDI_ICOTTBUP 138 +#define IDI_NEWMAIL 159 +#define IDD_CHOOSESTATUSMODES 310 +#define IDD_OPTIONS 311 +#define IDD_POP3ACCOUNTPOPUP 312 +#define IDC_EDITSERVER 1000 +#define IDC_EDITPORT 1001 +#define IDC_EDITLOGIN 1002 +#define IDC_EDITPASS 1003 +#define IDC_COMBOACCOUNT 1005 +#define IDC_BTNDEFAULT 1006 +#define IDC_EDITINTERVAL 1007 +#define IDC_CHECKSND 1008 +#define IDC_CHECKMSG 1009 +#define IDC_CHECKAPP 1010 +#define IDC_BTNAPP 1011 +#define IDC_CHECKICO 1012 +#define IDC_CHECK 1013 +#define IDC_BTNDEL 1014 +#define IDC_CHECKFSND 1016 +#define IDC_CHECKFMSG 1017 +#define IDC_CHECKFICO 1018 +#define IDC_CHECKST0 1019 +#define IDC_CHECKST1 1020 +#define IDC_CHECKST2 1021 +#define IDC_CHECKST3 1022 +#define IDC_CHECKST4 1023 +#define IDC_CHECKST5 1024 +#define IDC_CHECKST6 1025 +#define IDC_CHECKST7 1026 +#define IDC_EDITAPP 1027 +#define IDC_CHECKCONTACT 1030 +#define IDC_CHECKCONTACTNICK 1031 +#define IDC_CHECKCONTACTNOEVENT 1032 +#define IDC_STTIMELEFT 1033 +#define IDC_LISTMAILS 1038 +#define IDC_LISTHEADERS 1039 +#define IDC_EDITAPPPARAM 1044 +#define IDC_BTNOK 1047 +#define IDC_COMBOCP 1050 +#define IDC_STATICMSG 1055 +#define IDC_AUTOBODY 1062 +#define IDC_BTNRESET 1063 +#define IDC_CHECKSTART 1064 +#define IDC_STWCHECK 1065 +#define IDC_CHECKFORCE 1066 +#define IDC_RADIOPOP1 1068 +#define IDC_RADIOPOPN 1069 +#define IDC_CPB 1070 +#define IDC_CPNB 1071 +#define IDC_CHECKCOL 1073 +#define IDC_CPT 1074 +#define IDC_CPFB 1075 +#define IDC_CPFT 1076 +#define IDC_CHECKFCOL 1077 +#define IDC_CHECKNCOL 1078 +#define IDC_CPNT 1079 +#define IDC_CHECKPOP 1087 +#define IDC_CHECKNPOP 1088 +#define IDC_CHECKFPOP 1089 +#define IDC_EDITPOPS 1090 +#define IDC_EDITNPOPS 1091 +#define IDC_EDITFPOPS 1092 +#define IDC_GBNEWMAIL 1094 +#define IDC_GBNONEWMAIL 1095 +#define IDC_GBBADCONNECT 1096 +#define IDC_STSTATUS 1102 +#define IDC_CHECKTTB 1117 +#define IDC_CHECKSSL 1117 +#define IDC_CHECKNMSGP 1118 +#define IDC_CHECKNOTLS 1120 +#define IDC_CHECKKBN 1121 +#define IDC_BTNSTATUS 1123 +#define IDC_OPTIONSTAB 1124 +#define IDC_BTNCHECKALL 1125 +#define IDC_CLOSEONDELETE 1127 +#define IDC_LONGDATE 1128 +#define IDC_SMARTDATE 1129 +#define IDC_NOSECONDS 1130 +#define IDC_CHECKAPOP 1200 +#define IDC_STATUSGROUP 1338 +#define IDC_SPLITTER 1400 +#define IDC_EDITBODY 1401 +#define IDC_PREVIEW 1402 +#define IDC_BTNADD 1403 +#define IDC_EDITNAME 1404 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 144 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1407 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/YAMN/src/services.cpp b/protocols/YAMN/src/services.cpp index 822f5d9f31..361fb01636 100644 --- a/protocols/YAMN/src/services.cpp +++ b/protocols/YAMN/src/services.cpp @@ -1,378 +1,378 @@ -#include "stdafx.h" - -static INT_PTR Service_GetCaps(WPARAM wParam, LPARAM) -{ - switch (wParam) { - case PFLAGNUM_4: - return PF4_NOCUSTOMAUTH; - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)TranslateT("Nick"); - case PFLAG_MAXLENOFMESSAGE: - return 400; - case PFLAGNUM_2: - case PFLAGNUM_5: - return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND; - } - - return 0; -} - -static INT_PTR Service_GetName(WPARAM wParam, LPARAM lParam) -{ - mir_strncpy((char *)lParam, YAMN_DBMODULE, wParam); - return 0; -} - -static INT_PTR Service_LoadIcon(WPARAM wParam, LPARAM) -{ - if (LOWORD(wParam) == PLI_PROTOCOL) - return (INT_PTR)CopyIcon(g_plugin.getIcon(IDI_CHECKMAIL)); // noone cares about other than PLI_PROTOCOL - - return (INT_PTR)(HICON)NULL; -} - -INT_PTR ClistContactDoubleclicked(WPARAM, LPARAM lParam) -{ - ContactDoubleclicked(((CLISTEVENT *)lParam)->lParam, lParam); - return 0; -} - -static int Service_ContactDoubleclicked(WPARAM wParam, LPARAM lParam) -{ - ContactDoubleclicked(wParam, lParam); - return 0; -} - -static INT_PTR ContactApplication(WPARAM wParam, LPARAM) -{ - char *szProto = Proto_GetBaseAccountName(wParam); - if (mir_strcmp(szProto, YAMN_DBMODULE)) - return 0; - - DBVARIANT dbv; - if (g_plugin.getString(wParam, "Id", &dbv)) - return 0; - - CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - if (ActualAccount != nullptr) { - STARTUPINFOW si = {0}; - si.cb = sizeof(si); - - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - if (ActualAccount->NewMailN.App != nullptr) { - wchar_t *Command; - if (ActualAccount->NewMailN.AppParam != nullptr) - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; - else - Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; - - if (Command != nullptr) { - mir_wstrcpy(Command, L"\""); - mir_wstrcat(Command, ActualAccount->NewMailN.App); - mir_wstrcat(Command, L"\" "); - if (ActualAccount->NewMailN.AppParam != nullptr) - mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); - - PROCESS_INFORMATION pi; - CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); - delete[] Command; - } - } - - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - } - db_free(&dbv); - return 0; -} - -uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); -static INT_PTR AccountMailCheck(WPARAM wParam, LPARAM lParam) -{ - //This service will check/sincronize the account pointed by wParam - CAccount *ActualAccount = (CAccount *)wParam; - // copy/paste make mistakes - if (ActualAccount != nullptr) { - //we use event to signal, that running thread has all needed stack parameters copied - HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (ThreadRunningEV == nullptr) - return 0; - //if we want to close miranda, we get event and do not run pop3 checking anymore - if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) - return 0; - - mir_cslock lck(PluginRegCS); - if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualAccount->AccountAccessSO, 0)) { - } - else { - if ((ActualAccount->Flags & YAMN_ACC_ENA) && ActualAccount->Plugin->Fcn->SynchroFcnPtr) { - CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, lParam != 0 ? YAMN_FORCECHECK : YAMN_NORMALCHECK, nullptr, nullptr}; - - ActualAccount->TimeLeft = ActualAccount->Interval; - DWORD tid; - HANDLE NewThread = CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->SynchroFcnPtr, &ParamToPlugin, 0, &tid); - if (NewThread) { - WaitForSingleObject(ThreadRunningEV, INFINITE); - CloseHandle(NewThread); - } - } - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - CloseHandle(ThreadRunningEV); - } - return 0; -} - -static INT_PTR ContactMailCheck(WPARAM hContact, LPARAM) -{ - char *szProto = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(szProto, YAMN_DBMODULE)) - return 0; - - DBVARIANT dbv; - if (g_plugin.getString(hContact, "Id", &dbv)) - return 0; - - CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - if (ActualAccount != nullptr) { - //we use event to signal, that running thread has all needed stack parameters copied - HANDLE ThreadRunningEV; - if (nullptr == (ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr))) - return 0; - //if we want to close miranda, we get event and do not run pop3 checking anymore - if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) - return 0; - mir_cslock lck(PluginRegCS); - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { - } - else { - if ((ActualAccount->Flags & YAMN_ACC_ENA) && (ActualAccount->StatusFlags & YAMN_ACC_FORCE)) //account cannot be forced to check - { - if (ActualAccount->Plugin->Fcn->ForceCheckFcnPtr == nullptr) - ReadDoneFcn(ActualAccount->AccountAccessSO); - - DWORD tid; - struct CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_FORCECHECK, (void *)nullptr, nullptr}; - if (nullptr == CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->ForceCheckFcnPtr, &ParamToPlugin, 0, &tid)) - ReadDoneFcn(ActualAccount->AccountAccessSO); - else - WaitForSingleObject(ThreadRunningEV, INFINITE); - } - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - CloseHandle(ThreadRunningEV); - } - db_free(&dbv); - return 0; -} - -/*static*/ void ContactDoubleclicked(WPARAM wParam, LPARAM) -{ - char *szProto = Proto_GetBaseAccountName(wParam); - if (mir_strcmp(szProto, YAMN_DBMODULE)) - return; - - DBVARIANT dbv; - if (g_plugin.getString(wParam, "Id", &dbv)) - return; - - CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); - if (ActualAccount != nullptr) { - if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { - YAMN_MAILBROWSERPARAM Param = {nullptr, ActualAccount, ActualAccount->NewMailN.Flags, ActualAccount->NoNewMailN.Flags, nullptr}; - - Param.nnflags = Param.nnflags | YAMN_ACC_MSG; //show mails in account even no new mail in account - Param.nnflags = Param.nnflags & ~YAMN_ACC_POP; - - Param.nflags = Param.nflags | YAMN_ACC_MSG; //show mails in account even no new mail in account - Param.nflags = Param.nflags & ~YAMN_ACC_POP; - - RunMailBrowserSvc((WPARAM)&Param, YAMN_MAILBROWSERVERSION); - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - } - db_free(&dbv); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -HBITMAP LoadBmpFromIcon(HICON hIcon) -{ - int IconSizeX = 16; - int IconSizeY = 16; - - HBRUSH hBkgBrush = CreateSolidBrush(GetSysColor(COLOR_3DFACE)); - - BITMAPINFOHEADER bih = {0}; - bih.biSize = sizeof(bih); - bih.biBitCount = 24; - bih.biPlanes = 1; - bih.biCompression = BI_RGB; - bih.biHeight = IconSizeY; - bih.biWidth = IconSizeX; - - RECT rc; - rc.top = rc.left = 0; - rc.right = bih.biWidth; - rc.bottom = bih.biHeight; - - HDC hdc = GetDC(nullptr); - HBITMAP hBmp = CreateCompatibleBitmap(hdc, bih.biWidth, bih.biHeight); - HDC hdcMem = CreateCompatibleDC(hdc); - HBITMAP hoBmp = (HBITMAP)SelectObject(hdcMem, hBmp); - FillRect(hdcMem, &rc, hBkgBrush); - DrawIconEx(hdcMem, 0, 0, hIcon, bih.biWidth, bih.biHeight, 0, nullptr, DI_NORMAL); - SelectObject(hdcMem, hoBmp); - return hBmp; -} - -int AddTopToolbarIcon(WPARAM, LPARAM) -{ - if (g_plugin.getByte(YAMN_TTBFCHECK, 1)) { - if (ServiceExists(MS_TTB_REMOVEBUTTON) && hTTButton == nullptr) { - TTBButton btn = {}; - btn.pszService = MS_YAMN_FORCECHECK; - btn.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP; - btn.hIconHandleUp = btn.hIconHandleDn = g_plugin.getIconHandle(IDI_CHECKMAIL); - btn.name = btn.pszTooltipUp = LPGEN("Check mail"); - hTTButton = g_plugin.addTTB(&btn); - } - } - else { - if (hTTButton != nullptr) { - CallService(MS_TTB_REMOVEBUTTON, (WPARAM)hTTButton, 0); - hTTButton = nullptr; - } - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int Shutdown(WPARAM, LPARAM) -{ - CallService(MS_TTB_REMOVEBUTTON, (WPARAM)hTTButton, 0); - - g_plugin.setDword(YAMN_DBMSGPOSX, HeadPosX); - g_plugin.setDword(YAMN_DBMSGPOSY, HeadPosY); - g_plugin.setDword(YAMN_DBMSGSIZEX, HeadSizeX); - g_plugin.setDword(YAMN_DBMSGSIZEY, HeadSizeY); - g_plugin.setWord(YAMN_DBMSGPOSSPLIT, HeadSplitPos); - YAMNVar.Shutdown = TRUE; - KillTimer(nullptr, SecTimer); - - UnregisterProtoPlugins(); - UnregisterFilterPlugins(); - return 0; -} - -int SystemModulesLoaded(WPARAM, LPARAM); //in main.cpp - -void HookEvents(void) -{ - HookEvent(ME_SYSTEM_MODULESLOADED, SystemModulesLoaded); - HookEvent(ME_TTB_MODULELOADED, AddTopToolbarIcon); - HookEvent(ME_OPT_INITIALISE, YAMNOptInitSvc); - HookEvent(ME_SYSTEM_PRESHUTDOWN, Shutdown); - HookEvent(ME_CLIST_DOUBLECLICKED, Service_ContactDoubleclicked); -} - -void CreateServiceFunctions(void) -{ - // Standard 'protocol' services - CreateServiceFunction(YAMN_DBMODULE PS_GETCAPS, Service_GetCaps); - CreateServiceFunction(YAMN_DBMODULE PS_GETNAME, Service_GetName); - CreateServiceFunction(YAMN_DBMODULE PS_LOADICON, Service_LoadIcon); - - // Function with which protocol plugin can register - CreateServiceFunction(MS_YAMN_GETFCNPTR, GetFcnPtrSvc); - - // Function returns pointer to YAMN variables - CreateServiceFunction(MS_YAMN_GETVARIABLES, GetVariablesSvc); - - // Function with which protocol plugin can register - CreateServiceFunction(MS_YAMN_REGISTERPROTOPLUGIN, RegisterProtocolPluginSvc); - - // Function with which protocol plugin can unregister - CreateServiceFunction(MS_YAMN_UNREGISTERPROTOPLUGIN, UnregisterProtocolPluginSvc); - - // Function creates an account for plugin - CreateServiceFunction(MS_YAMN_CREATEPLUGINACCOUNT, CreatePluginAccountSvc); - - // Function deletes plugin account - CreateServiceFunction(MS_YAMN_DELETEPLUGINACCOUNT, DeletePluginAccountSvc); - - // Finds account for plugin by name - CreateServiceFunction(MS_YAMN_FINDACCOUNTBYNAME, FindAccountByNameSvc); - - // Creates next account for plugin - CreateServiceFunction(MS_YAMN_GETNEXTFREEACCOUNT, GetNextFreeAccountSvc); - - // Function removes account from YAMN queue. Does not delete it from memory - CreateServiceFunction(MS_YAMN_DELETEACCOUNT, DeleteAccountSvc); - - // Function finds accounts for specified plugin - CreateServiceFunction(MS_YAMN_READACCOUNTS, AddAccountsFromFileSvc); - - // Function that stores all plugin mails to one file - CreateServiceFunction(MS_YAMN_WRITEACCOUNTS, WriteAccountsToFileSvc); - - // Function that returns user's filename - CreateServiceFunction(MS_YAMN_GETFILENAME, GetFileNameSvc); - - // Releases unicode string from memory - CreateServiceFunction(MS_YAMN_DELETEFILENAME, DeleteFileNameSvc); - - // Checks mail - CreateServiceFunction(MS_YAMN_FORCECHECK, ForceCheckSvc); - - // Runs YAMN's mail browser - CreateServiceFunction(MS_YAMN_MAILBROWSER, RunMailBrowserSvc); - - // Function creates new mail for plugin - CreateServiceFunction(MS_YAMN_CREATEACCOUNTMAIL, CreateAccountMailSvc); - - // Function deletes plugin account - CreateServiceFunction(MS_YAMN_DELETEACCOUNTMAIL, DeleteAccountMailSvc); - - // Function with which filter plugin can register - CreateServiceFunction(MS_YAMN_REGISTERFILTERPLUGIN, RegisterFilterPluginSvc); - - // Function with which filter plugin can unregister - CreateServiceFunction(MS_YAMN_UNREGISTERFILTERPLUGIN, UnregisterFilterPluginSvc); - - // Function filters mail - CreateServiceFunction(MS_YAMN_FILTERMAIL, FilterMailSvc); - - // Function contact list double click - CreateServiceFunction(MS_YAMN_CLISTDBLCLICK, ClistContactDoubleclicked); - - // Function to check individual account - CreateServiceFunction(MS_YAMN_ACCOUNTCHECK, AccountMailCheck); - - // Function contact list context menu click - CreateServiceFunction(MS_YAMN_CLISTCONTEXT, ContactMailCheck); - - // Function contact list context menu click - CreateServiceFunction(MS_YAMN_CLISTCONTEXTAPP, ContactApplication); -} - -// Function to put all enabled contact to the Online status -void RefreshContact(void) -{ - CAccount *Finder; - for (Finder = POP3Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { - if (Finder->hContact != NULL) { - Contact::Hide(Finder->hContact, !(Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)); - } - else if ((Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) { - Finder->hContact = db_add_contact(); - Proto_AddToContact(Finder->hContact, YAMN_DBMODULE); - g_plugin.setString(Finder->hContact, "Id", Finder->Name); - g_plugin.setString(Finder->hContact, "Nick", Finder->Name); - g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_ONLINE); - db_set_s(Finder->hContact, "CList", "StatusMsg", Translate("No new mail message")); - } - } -} +#include "stdafx.h" + +static INT_PTR Service_GetCaps(WPARAM wParam, LPARAM) +{ + switch (wParam) { + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)TranslateT("Nick"); + case PFLAG_MAXLENOFMESSAGE: + return 400; + case PFLAGNUM_2: + case PFLAGNUM_5: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND; + } + + return 0; +} + +static INT_PTR Service_GetName(WPARAM wParam, LPARAM lParam) +{ + mir_strncpy((char *)lParam, YAMN_DBMODULE, wParam); + return 0; +} + +static INT_PTR Service_LoadIcon(WPARAM wParam, LPARAM) +{ + if (LOWORD(wParam) == PLI_PROTOCOL) + return (INT_PTR)CopyIcon(g_plugin.getIcon(IDI_CHECKMAIL)); // noone cares about other than PLI_PROTOCOL + + return (INT_PTR)(HICON)NULL; +} + +INT_PTR ClistContactDoubleclicked(WPARAM, LPARAM lParam) +{ + ContactDoubleclicked(((CLISTEVENT *)lParam)->lParam, lParam); + return 0; +} + +static int Service_ContactDoubleclicked(WPARAM wParam, LPARAM lParam) +{ + ContactDoubleclicked(wParam, lParam); + return 0; +} + +static INT_PTR ContactApplication(WPARAM wParam, LPARAM) +{ + char *szProto = Proto_GetBaseAccountName(wParam); + if (mir_strcmp(szProto, YAMN_DBMODULE)) + return 0; + + DBVARIANT dbv; + if (g_plugin.getString(wParam, "Id", &dbv)) + return 0; + + CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + if (ActualAccount != nullptr) { + STARTUPINFOW si = {0}; + si.cb = sizeof(si); + + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + if (ActualAccount->NewMailN.App != nullptr) { + wchar_t *Command; + if (ActualAccount->NewMailN.AppParam != nullptr) + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + mir_wstrlen(ActualAccount->NewMailN.AppParam) + 6]; + else + Command = new wchar_t[mir_wstrlen(ActualAccount->NewMailN.App) + 6]; + + if (Command != nullptr) { + mir_wstrcpy(Command, L"\""); + mir_wstrcat(Command, ActualAccount->NewMailN.App); + mir_wstrcat(Command, L"\" "); + if (ActualAccount->NewMailN.AppParam != nullptr) + mir_wstrcat(Command, ActualAccount->NewMailN.AppParam); + + PROCESS_INFORMATION pi; + CreateProcessW(nullptr, Command, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + delete[] Command; + } + } + + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + } + db_free(&dbv); + return 0; +} + +uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); +static INT_PTR AccountMailCheck(WPARAM wParam, LPARAM lParam) +{ + //This service will check/sincronize the account pointed by wParam + CAccount *ActualAccount = (CAccount *)wParam; + // copy/paste make mistakes + if (ActualAccount != nullptr) { + //we use event to signal, that running thread has all needed stack parameters copied + HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (ThreadRunningEV == nullptr) + return 0; + //if we want to close miranda, we get event and do not run pop3 checking anymore + if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) + return 0; + + mir_cslock lck(PluginRegCS); + if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualAccount->AccountAccessSO, 0)) { + } + else { + if ((ActualAccount->Flags & YAMN_ACC_ENA) && ActualAccount->Plugin->Fcn->SynchroFcnPtr) { + CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, lParam != 0 ? YAMN_FORCECHECK : YAMN_NORMALCHECK, nullptr, nullptr}; + + ActualAccount->TimeLeft = ActualAccount->Interval; + DWORD tid; + HANDLE NewThread = CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->SynchroFcnPtr, &ParamToPlugin, 0, &tid); + if (NewThread) { + WaitForSingleObject(ThreadRunningEV, INFINITE); + CloseHandle(NewThread); + } + } + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + CloseHandle(ThreadRunningEV); + } + return 0; +} + +static INT_PTR ContactMailCheck(WPARAM hContact, LPARAM) +{ + char *szProto = Proto_GetBaseAccountName(hContact); + if (mir_strcmp(szProto, YAMN_DBMODULE)) + return 0; + + DBVARIANT dbv; + if (g_plugin.getString(hContact, "Id", &dbv)) + return 0; + + CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + if (ActualAccount != nullptr) { + //we use event to signal, that running thread has all needed stack parameters copied + HANDLE ThreadRunningEV; + if (nullptr == (ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr))) + return 0; + //if we want to close miranda, we get event and do not run pop3 checking anymore + if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) + return 0; + mir_cslock lck(PluginRegCS); + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) { + } + else { + if ((ActualAccount->Flags & YAMN_ACC_ENA) && (ActualAccount->StatusFlags & YAMN_ACC_FORCE)) //account cannot be forced to check + { + if (ActualAccount->Plugin->Fcn->ForceCheckFcnPtr == nullptr) + ReadDoneFcn(ActualAccount->AccountAccessSO); + + DWORD tid; + struct CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_FORCECHECK, (void *)nullptr, nullptr}; + if (nullptr == CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->ForceCheckFcnPtr, &ParamToPlugin, 0, &tid)) + ReadDoneFcn(ActualAccount->AccountAccessSO); + else + WaitForSingleObject(ThreadRunningEV, INFINITE); + } + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + CloseHandle(ThreadRunningEV); + } + db_free(&dbv); + return 0; +} + +/*static*/ void ContactDoubleclicked(WPARAM wParam, LPARAM) +{ + char *szProto = Proto_GetBaseAccountName(wParam); + if (mir_strcmp(szProto, YAMN_DBMODULE)) + return; + + DBVARIANT dbv; + if (g_plugin.getString(wParam, "Id", &dbv)) + return; + + CAccount *ActualAccount = (CAccount *)CallService(MS_YAMN_FINDACCOUNTBYNAME, (WPARAM)POP3Plugin, (LPARAM)dbv.pszVal); + if (ActualAccount != nullptr) { + if (WAIT_OBJECT_0 == WaitToReadFcn(ActualAccount->AccountAccessSO)) { + YAMN_MAILBROWSERPARAM Param = {nullptr, ActualAccount, ActualAccount->NewMailN.Flags, ActualAccount->NoNewMailN.Flags, nullptr}; + + Param.nnflags = Param.nnflags | YAMN_ACC_MSG; //show mails in account even no new mail in account + Param.nnflags = Param.nnflags & ~YAMN_ACC_POP; + + Param.nflags = Param.nflags | YAMN_ACC_MSG; //show mails in account even no new mail in account + Param.nflags = Param.nflags & ~YAMN_ACC_POP; + + RunMailBrowserSvc((WPARAM)&Param, YAMN_MAILBROWSERVERSION); + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + } + db_free(&dbv); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +HBITMAP LoadBmpFromIcon(HICON hIcon) +{ + int IconSizeX = 16; + int IconSizeY = 16; + + HBRUSH hBkgBrush = CreateSolidBrush(GetSysColor(COLOR_3DFACE)); + + BITMAPINFOHEADER bih = {0}; + bih.biSize = sizeof(bih); + bih.biBitCount = 24; + bih.biPlanes = 1; + bih.biCompression = BI_RGB; + bih.biHeight = IconSizeY; + bih.biWidth = IconSizeX; + + RECT rc; + rc.top = rc.left = 0; + rc.right = bih.biWidth; + rc.bottom = bih.biHeight; + + HDC hdc = GetDC(nullptr); + HBITMAP hBmp = CreateCompatibleBitmap(hdc, bih.biWidth, bih.biHeight); + HDC hdcMem = CreateCompatibleDC(hdc); + HBITMAP hoBmp = (HBITMAP)SelectObject(hdcMem, hBmp); + FillRect(hdcMem, &rc, hBkgBrush); + DrawIconEx(hdcMem, 0, 0, hIcon, bih.biWidth, bih.biHeight, 0, nullptr, DI_NORMAL); + SelectObject(hdcMem, hoBmp); + return hBmp; +} + +int AddTopToolbarIcon(WPARAM, LPARAM) +{ + if (g_plugin.getByte(YAMN_TTBFCHECK, 1)) { + if (ServiceExists(MS_TTB_REMOVEBUTTON) && hTTButton == nullptr) { + TTBButton btn = {}; + btn.pszService = MS_YAMN_FORCECHECK; + btn.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP; + btn.hIconHandleUp = btn.hIconHandleDn = g_plugin.getIconHandle(IDI_CHECKMAIL); + btn.name = btn.pszTooltipUp = LPGEN("Check mail"); + hTTButton = g_plugin.addTTB(&btn); + } + } + else { + if (hTTButton != nullptr) { + CallService(MS_TTB_REMOVEBUTTON, (WPARAM)hTTButton, 0); + hTTButton = nullptr; + } + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int Shutdown(WPARAM, LPARAM) +{ + CallService(MS_TTB_REMOVEBUTTON, (WPARAM)hTTButton, 0); + + g_plugin.setDword(YAMN_DBMSGPOSX, HeadPosX); + g_plugin.setDword(YAMN_DBMSGPOSY, HeadPosY); + g_plugin.setDword(YAMN_DBMSGSIZEX, HeadSizeX); + g_plugin.setDword(YAMN_DBMSGSIZEY, HeadSizeY); + g_plugin.setWord(YAMN_DBMSGPOSSPLIT, HeadSplitPos); + YAMNVar.Shutdown = TRUE; + KillTimer(nullptr, SecTimer); + + UnregisterProtoPlugins(); + UnregisterFilterPlugins(); + return 0; +} + +int SystemModulesLoaded(WPARAM, LPARAM); //in main.cpp + +void HookEvents(void) +{ + HookEvent(ME_SYSTEM_MODULESLOADED, SystemModulesLoaded); + HookEvent(ME_TTB_MODULELOADED, AddTopToolbarIcon); + HookEvent(ME_OPT_INITIALISE, YAMNOptInitSvc); + HookEvent(ME_SYSTEM_PRESHUTDOWN, Shutdown); + HookEvent(ME_CLIST_DOUBLECLICKED, Service_ContactDoubleclicked); +} + +void CreateServiceFunctions(void) +{ + // Standard 'protocol' services + CreateServiceFunction(YAMN_DBMODULE PS_GETCAPS, Service_GetCaps); + CreateServiceFunction(YAMN_DBMODULE PS_GETNAME, Service_GetName); + CreateServiceFunction(YAMN_DBMODULE PS_LOADICON, Service_LoadIcon); + + // Function with which protocol plugin can register + CreateServiceFunction(MS_YAMN_GETFCNPTR, GetFcnPtrSvc); + + // Function returns pointer to YAMN variables + CreateServiceFunction(MS_YAMN_GETVARIABLES, GetVariablesSvc); + + // Function with which protocol plugin can register + CreateServiceFunction(MS_YAMN_REGISTERPROTOPLUGIN, RegisterProtocolPluginSvc); + + // Function with which protocol plugin can unregister + CreateServiceFunction(MS_YAMN_UNREGISTERPROTOPLUGIN, UnregisterProtocolPluginSvc); + + // Function creates an account for plugin + CreateServiceFunction(MS_YAMN_CREATEPLUGINACCOUNT, CreatePluginAccountSvc); + + // Function deletes plugin account + CreateServiceFunction(MS_YAMN_DELETEPLUGINACCOUNT, DeletePluginAccountSvc); + + // Finds account for plugin by name + CreateServiceFunction(MS_YAMN_FINDACCOUNTBYNAME, FindAccountByNameSvc); + + // Creates next account for plugin + CreateServiceFunction(MS_YAMN_GETNEXTFREEACCOUNT, GetNextFreeAccountSvc); + + // Function removes account from YAMN queue. Does not delete it from memory + CreateServiceFunction(MS_YAMN_DELETEACCOUNT, DeleteAccountSvc); + + // Function finds accounts for specified plugin + CreateServiceFunction(MS_YAMN_READACCOUNTS, AddAccountsFromFileSvc); + + // Function that stores all plugin mails to one file + CreateServiceFunction(MS_YAMN_WRITEACCOUNTS, WriteAccountsToFileSvc); + + // Function that returns user's filename + CreateServiceFunction(MS_YAMN_GETFILENAME, GetFileNameSvc); + + // Releases unicode string from memory + CreateServiceFunction(MS_YAMN_DELETEFILENAME, DeleteFileNameSvc); + + // Checks mail + CreateServiceFunction(MS_YAMN_FORCECHECK, ForceCheckSvc); + + // Runs YAMN's mail browser + CreateServiceFunction(MS_YAMN_MAILBROWSER, RunMailBrowserSvc); + + // Function creates new mail for plugin + CreateServiceFunction(MS_YAMN_CREATEACCOUNTMAIL, CreateAccountMailSvc); + + // Function deletes plugin account + CreateServiceFunction(MS_YAMN_DELETEACCOUNTMAIL, DeleteAccountMailSvc); + + // Function with which filter plugin can register + CreateServiceFunction(MS_YAMN_REGISTERFILTERPLUGIN, RegisterFilterPluginSvc); + + // Function with which filter plugin can unregister + CreateServiceFunction(MS_YAMN_UNREGISTERFILTERPLUGIN, UnregisterFilterPluginSvc); + + // Function filters mail + CreateServiceFunction(MS_YAMN_FILTERMAIL, FilterMailSvc); + + // Function contact list double click + CreateServiceFunction(MS_YAMN_CLISTDBLCLICK, ClistContactDoubleclicked); + + // Function to check individual account + CreateServiceFunction(MS_YAMN_ACCOUNTCHECK, AccountMailCheck); + + // Function contact list context menu click + CreateServiceFunction(MS_YAMN_CLISTCONTEXT, ContactMailCheck); + + // Function contact list context menu click + CreateServiceFunction(MS_YAMN_CLISTCONTEXTAPP, ContactApplication); +} + +// Function to put all enabled contact to the Online status +void RefreshContact(void) +{ + CAccount *Finder; + for (Finder = POP3Plugin->FirstAccount; Finder != nullptr; Finder = Finder->Next) { + if (Finder->hContact != NULL) { + Contact::Hide(Finder->hContact, !(Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)); + } + else if ((Finder->Flags & YAMN_ACC_ENA) && (Finder->NewMailN.Flags & YAMN_ACC_CONT)) { + Finder->hContact = db_add_contact(); + Proto_AddToContact(Finder->hContact, YAMN_DBMODULE); + g_plugin.setString(Finder->hContact, "Id", Finder->Name); + g_plugin.setString(Finder->hContact, "Nick", Finder->Name); + g_plugin.setWord(Finder->hContact, "Status", ID_STATUS_ONLINE); + db_set_s(Finder->hContact, "CList", "StatusMsg", Translate("No new mail message")); + } + } +} diff --git a/protocols/YAMN/src/stdafx.h b/protocols/YAMN/src/stdafx.h index 05681d467b..38f1c1b5fe 100644 --- a/protocols/YAMN/src/stdafx.h +++ b/protocols/YAMN/src/stdafx.h @@ -1,253 +1,253 @@ - -#ifndef __YAMN_H -#define __YAMN_H - -#define VC_EXTRALEAN - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "mails/decode.h" -#include "browser/browser.h" -#include "resource.h" -#include "debug.h" -#include "version.h" -#include "proto/netclient.h" -#include "proto/netlib.h" -#include "proto/pop3/pop3.h" -#include "proto/pop3/pop3comm.h" - -struct CMPlugin : public PLUGIN -{ - CMPlugin(); - - int Load() override; - int Unload() override; -}; - -// From services.cpp -void CreateServiceFunctions(void); -void HookEvents(void); -void RefreshContact(void); -void ContactDoubleclicked(WPARAM wParam, LPARAM lParam); -INT_PTR ClistContactDoubleclicked(WPARAM wParam, LPARAM lParam); - -extern mir_cs PluginRegCS; -extern SCOUNTER *AccountWriterSO; -extern HANDLE ExitEV; -extern HANDLE WriteToFileEV; - -// From debug.cpp -#ifdef _DEBUG -void InitDebug(); -void UnInitDebug(); -#endif - -// From yamn.cpp -INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM lParam); -INT_PTR GetVariablesSvc(WPARAM, LPARAM); -void CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD); -INT_PTR ForceCheckSvc(WPARAM, LPARAM); - -extern struct YAMNExportedFcns *pYAMNFcn; - -// From account.cpp -INT_PTR CreatePluginAccountSvc(WPARAM wParam, LPARAM lParam); -INT_PTR DeletePluginAccountSvc(WPARAM wParam, LPARAM); -int InitAccount(CAccount *Which); -void DeInitAccount(CAccount *Which); -void StopSignalFcn(CAccount *Which); -void CodeDecodeString(char *Dest, BOOL Encrypt); -uint32_t FileToMemory(wchar_t *FileName, char **MemFile, char **End); - -#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) -uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo,char *DebugString); -#endif -uint32_t ReadStringFromMemory(char **Parser, char *End, char **StoreTo); -uint32_t ReadMessagesFromMemory(CAccount *Which, char **Parser, char *End); -uint32_t ReadAccountFromMemory(CAccount *Which, char **Parser, wchar_t *End); -INT_PTR AddAccountsFromFileSvc(WPARAM wParam, LPARAM lParam); - -uint32_t WriteStringToFile(HANDLE File, char *Source); -uint32_t WriteStringToFileW(HANDLE File, wchar_t *Source); - - -DWORD WriteMessagesToFile(HANDLE File, CAccount *Which); -DWORD WINAPI WritePOP3Accounts(); -INT_PTR WriteAccountsToFileSvc(WPARAM wParam, LPARAM lParam); -INT_PTR FindAccountByNameSvc(WPARAM wParam, LPARAM lParam); -INT_PTR GetNextFreeAccountSvc(WPARAM wParam, LPARAM lParam); - -INT_PTR DeleteAccountSvc(WPARAM wParam, LPARAM); -void __cdecl DeleteAccountInBackground(void *Which); -int StopAccounts(HYAMNPROTOPLUGIN Plugin); -int WaitForAllAccounts(HYAMNPROTOPLUGIN Plugin, BOOL GetAccountBrowserAccess = FALSE); -int DeleteAccounts(HYAMNPROTOPLUGIN Plugin); - -void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value); -void WINAPI SetStatusFcn(CAccount *Which, wchar_t *Value); - -INT_PTR UnregisterProtoPlugins(); -INT_PTR RegisterProtocolPluginSvc(WPARAM, LPARAM); -INT_PTR UnregisterProtocolPluginSvc(WPARAM, LPARAM); -INT_PTR GetFileNameSvc(WPARAM, LPARAM); -INT_PTR DeleteFileNameSvc(WPARAM, LPARAM); - -//From filterplugin.cpp -//struct CExportedFunctions FilterPluginExported[]; -INT_PTR UnregisterFilterPlugins(); -INT_PTR RegisterFilterPluginSvc(WPARAM, LPARAM); -INT_PTR UnregisterFilterPluginSvc(WPARAM, LPARAM); -INT_PTR FilterMailSvc(WPARAM, LPARAM); - -//From mails.cpp (MIME) -//struct CExportedFunctions MailExported[]; -INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam); -INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam); -INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam); -INT_PTR UnloadMailDataSvc(WPARAM wParam, LPARAM); -INT_PTR SaveMailDataSvc(WPARAM wParam, LPARAM lParam); - -//From mime.cpp -//void WINAPI ExtractHeaderFcn(char *,int,uint16_t,HYAMNMAIL); //already in MailExported -struct _tcptable -{ - char *NameBase, *NameSub; - BOOLEAN isValid; - unsigned short int CP; -}; -extern struct _tcptable CodePageNamesAll[]; // in mime/decode.cpp -extern int CPLENALL; -extern struct _tcptable *CodePageNamesSupp; // in mime/decode.cpp -extern int CPLENSUPP; - -extern int PosX, PosY, SizeX, SizeY; -extern int HeadPosX, HeadPosY, HeadSizeX, HeadSizeY, HeadSplitPos; - -//#define CPDEFINDEX 63 //ISO-8859-1 -#define CPDEFINDEX 0 //ACP - -//From pop3comm.cpp -int RegisterPOP3Plugin(WPARAM, LPARAM); - -//From mailbrowser.cpp -INT_PTR RunMailBrowserSvc(WPARAM, LPARAM); - -//From badconnect.cpp -int RunBadConnection(CAccount *acc, UINT_PTR iErrorCode, void *pUserInfo); - -//From YAMNopts.cpp -int YAMNOptInitSvc(WPARAM, LPARAM); - -//From main.cpp -int PostLoad(WPARAM, LPARAM); //Executed after all plugins loaded YAMN reads mails from file and notify every protocol it should set its functions -int Shutdown(WPARAM, LPARAM); //Executed before Miranda is going to shutdown -int AddTopToolbarIcon(WPARAM, LPARAM); //Executed when TopToolBar plugin loaded Adds bitmap to toolbar - -extern wchar_t UserDirectory[]; //e.g. "F:\WINNT\Profiles\UserXYZ" -extern wchar_t ProfileName[]; //e.g. "majvan" -extern SWMRG *AccountBrowserSO; -extern YAMN_VARIABLES YAMNVar; -extern HANDLE hNewMailHook; -extern HANDLE hTTButton; -extern HCURSOR hCurSplitNS, hCurSplitWE; -extern UINT SecTimer; - -//From synchro.cpp -void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From); -uint32_t WINAPI WaitToWriteFcn(PSWMRG SObject, PSCOUNTER SCounter = nullptr); -void WINAPI WriteDoneFcn(PSWMRG SObject, PSCOUNTER SCounter = nullptr); -uint32_t WINAPI WaitToReadFcn(PSWMRG SObject); -void WINAPI ReadDoneFcn(PSWMRG SObject); -uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter); -uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter); -BOOL WINAPI SWMRGInitialize(PSWMRG, wchar_t *); -void WINAPI SWMRGDelete(PSWMRG); -uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout); -void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG); -uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); -void WINAPI SWMRGDoneReading(PSWMRG pSWMRG); - -//From mails.cpp -void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode); -void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSet, int mode); - -//From mime.cpp -void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head); -void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head); -void DeleteHeaderContent(struct CHeader *head); -void DeleteShortHeaderContent(struct CShortHeader *head); -char *ExtractFromContentType(char *ContentType, char *value); -wchar_t *ParseMultipartBody(char *src, char *bond); - -//From account.cpp -void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value); - -extern HYAMNPROTOPLUGIN POP3Plugin; - -//from decode.cpp -int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); -int DecodeBase64(char *Src, char *Dst, int DstLen); - -//From filterplugin.cpp -extern PYAMN_FILTERPLUGINQUEUE FirstFilterPlugin; - -//From protoplugin.cpp -extern PYAMN_PROTOPLUGINQUEUE FirstProtoPlugin; - -extern struct CExportedFunctions ProtoPluginExportedFcn[1]; -extern struct CExportedServices ProtoPluginExportedSvc[5]; -//From filterplugin.cpp -extern struct CExportedFunctions FilterPluginExportedFcn[1]; -extern struct CExportedServices FilterPluginExportedSvc[2]; -//From synchro.cpp -extern struct CExportedFunctions SynchroExportedFcn[7]; -//From account.cpp -extern struct CExportedFunctions AccountExportedFcn[2]; -extern struct CExportedServices AccountExportedSvc[9]; -//From mails.cpp (MIME) -extern struct CExportedFunctions MailExportedFcn[8]; -extern struct CExportedServices MailExportedSvc[5]; - -extern char *iconDescs[]; -extern char *iconNames[]; -extern HIMAGELIST CSImages; - -extern void __stdcall SSL_DebugLog(const char *fmt, ...); - -extern struct WndHandles *MessageWnd; - -extern int GetCharsetFromString(char *input, size_t size); -extern void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); -extern void __cdecl MailBrowser(void *Param); -extern void __cdecl BadConnection(void *Param); -extern PVOID TLSCtx; -extern PVOID SSLCtx; - -extern PYAMN_VARIABLES pYAMNVar; - -#endif + +#ifndef __YAMN_H +#define __YAMN_H + +#define VC_EXTRALEAN + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "mails/decode.h" +#include "browser/browser.h" +#include "resource.h" +#include "debug.h" +#include "version.h" +#include "proto/netclient.h" +#include "proto/netlib.h" +#include "proto/pop3/pop3.h" +#include "proto/pop3/pop3comm.h" + +struct CMPlugin : public PLUGIN +{ + CMPlugin(); + + int Load() override; + int Unload() override; +}; + +// From services.cpp +void CreateServiceFunctions(void); +void HookEvents(void); +void RefreshContact(void); +void ContactDoubleclicked(WPARAM wParam, LPARAM lParam); +INT_PTR ClistContactDoubleclicked(WPARAM wParam, LPARAM lParam); + +extern mir_cs PluginRegCS; +extern SCOUNTER *AccountWriterSO; +extern HANDLE ExitEV; +extern HANDLE WriteToFileEV; + +// From debug.cpp +#ifdef _DEBUG +void InitDebug(); +void UnInitDebug(); +#endif + +// From yamn.cpp +INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM lParam); +INT_PTR GetVariablesSvc(WPARAM, LPARAM); +void CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD); +INT_PTR ForceCheckSvc(WPARAM, LPARAM); + +extern struct YAMNExportedFcns *pYAMNFcn; + +// From account.cpp +INT_PTR CreatePluginAccountSvc(WPARAM wParam, LPARAM lParam); +INT_PTR DeletePluginAccountSvc(WPARAM wParam, LPARAM); +int InitAccount(CAccount *Which); +void DeInitAccount(CAccount *Which); +void StopSignalFcn(CAccount *Which); +void CodeDecodeString(char *Dest, BOOL Encrypt); +uint32_t FileToMemory(wchar_t *FileName, char **MemFile, char **End); + +#if defined(DEBUG_FILEREAD) || defined(DEBUG_FILEREADMESSAGES) +uint32_t ReadStringFromMemory(char **Parser,char *End,char **StoreTo,char *DebugString); +#endif +uint32_t ReadStringFromMemory(char **Parser, char *End, char **StoreTo); +uint32_t ReadMessagesFromMemory(CAccount *Which, char **Parser, char *End); +uint32_t ReadAccountFromMemory(CAccount *Which, char **Parser, wchar_t *End); +INT_PTR AddAccountsFromFileSvc(WPARAM wParam, LPARAM lParam); + +uint32_t WriteStringToFile(HANDLE File, char *Source); +uint32_t WriteStringToFileW(HANDLE File, wchar_t *Source); + + +DWORD WriteMessagesToFile(HANDLE File, CAccount *Which); +DWORD WINAPI WritePOP3Accounts(); +INT_PTR WriteAccountsToFileSvc(WPARAM wParam, LPARAM lParam); +INT_PTR FindAccountByNameSvc(WPARAM wParam, LPARAM lParam); +INT_PTR GetNextFreeAccountSvc(WPARAM wParam, LPARAM lParam); + +INT_PTR DeleteAccountSvc(WPARAM wParam, LPARAM); +void __cdecl DeleteAccountInBackground(void *Which); +int StopAccounts(HYAMNPROTOPLUGIN Plugin); +int WaitForAllAccounts(HYAMNPROTOPLUGIN Plugin, BOOL GetAccountBrowserAccess = FALSE); +int DeleteAccounts(HYAMNPROTOPLUGIN Plugin); + +void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value); +void WINAPI SetStatusFcn(CAccount *Which, wchar_t *Value); + +INT_PTR UnregisterProtoPlugins(); +INT_PTR RegisterProtocolPluginSvc(WPARAM, LPARAM); +INT_PTR UnregisterProtocolPluginSvc(WPARAM, LPARAM); +INT_PTR GetFileNameSvc(WPARAM, LPARAM); +INT_PTR DeleteFileNameSvc(WPARAM, LPARAM); + +//From filterplugin.cpp +//struct CExportedFunctions FilterPluginExported[]; +INT_PTR UnregisterFilterPlugins(); +INT_PTR RegisterFilterPluginSvc(WPARAM, LPARAM); +INT_PTR UnregisterFilterPluginSvc(WPARAM, LPARAM); +INT_PTR FilterMailSvc(WPARAM, LPARAM); + +//From mails.cpp (MIME) +//struct CExportedFunctions MailExported[]; +INT_PTR CreateAccountMailSvc(WPARAM wParam, LPARAM lParam); +INT_PTR DeleteAccountMailSvc(WPARAM wParam, LPARAM lParam); +INT_PTR LoadMailDataSvc(WPARAM wParam, LPARAM lParam); +INT_PTR UnloadMailDataSvc(WPARAM wParam, LPARAM); +INT_PTR SaveMailDataSvc(WPARAM wParam, LPARAM lParam); + +//From mime.cpp +//void WINAPI ExtractHeaderFcn(char *,int,uint16_t,HYAMNMAIL); //already in MailExported +struct _tcptable +{ + char *NameBase, *NameSub; + BOOLEAN isValid; + unsigned short int CP; +}; +extern struct _tcptable CodePageNamesAll[]; // in mime/decode.cpp +extern int CPLENALL; +extern struct _tcptable *CodePageNamesSupp; // in mime/decode.cpp +extern int CPLENSUPP; + +extern int PosX, PosY, SizeX, SizeY; +extern int HeadPosX, HeadPosY, HeadSizeX, HeadSizeY, HeadSplitPos; + +//#define CPDEFINDEX 63 //ISO-8859-1 +#define CPDEFINDEX 0 //ACP + +//From pop3comm.cpp +int RegisterPOP3Plugin(WPARAM, LPARAM); + +//From mailbrowser.cpp +INT_PTR RunMailBrowserSvc(WPARAM, LPARAM); + +//From badconnect.cpp +int RunBadConnection(CAccount *acc, UINT_PTR iErrorCode, void *pUserInfo); + +//From YAMNopts.cpp +int YAMNOptInitSvc(WPARAM, LPARAM); + +//From main.cpp +int PostLoad(WPARAM, LPARAM); //Executed after all plugins loaded YAMN reads mails from file and notify every protocol it should set its functions +int Shutdown(WPARAM, LPARAM); //Executed before Miranda is going to shutdown +int AddTopToolbarIcon(WPARAM, LPARAM); //Executed when TopToolBar plugin loaded Adds bitmap to toolbar + +extern wchar_t UserDirectory[]; //e.g. "F:\WINNT\Profiles\UserXYZ" +extern wchar_t ProfileName[]; //e.g. "majvan" +extern SWMRG *AccountBrowserSO; +extern YAMN_VARIABLES YAMNVar; +extern HANDLE hNewMailHook; +extern HANDLE hTTButton; +extern HCURSOR hCurSplitNS, hCurSplitWE; +extern UINT SecTimer; + +//From synchro.cpp +void WINAPI DeleteMessagesToEndFcn(CAccount *Account, HYAMNMAIL From); +uint32_t WINAPI WaitToWriteFcn(PSWMRG SObject, PSCOUNTER SCounter = nullptr); +void WINAPI WriteDoneFcn(PSWMRG SObject, PSCOUNTER SCounter = nullptr); +uint32_t WINAPI WaitToReadFcn(PSWMRG SObject); +void WINAPI ReadDoneFcn(PSWMRG SObject); +uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter); +uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter); +BOOL WINAPI SWMRGInitialize(PSWMRG, wchar_t *); +void WINAPI SWMRGDelete(PSWMRG); +uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout); +void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG); +uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); +void WINAPI SWMRGDoneReading(PSWMRG pSWMRG); + +//From mails.cpp +void WINAPI DeleteMessageFromQueueFcn(HYAMNMAIL *From, HYAMNMAIL Which, int mode); +void WINAPI SetRemoveFlagsInQueueFcn(HYAMNMAIL From, uint32_t FlagsSet, uint32_t FlagsNotSet, uint32_t FlagsToSet, int mode); + +//From mime.cpp +void ExtractHeader(struct CMimeItem *items, int &CP, struct CHeader *head); +void ExtractShortHeader(struct CMimeItem *items, struct CShortHeader *head); +void DeleteHeaderContent(struct CHeader *head); +void DeleteShortHeaderContent(struct CShortHeader *head); +char *ExtractFromContentType(char *ContentType, char *value); +wchar_t *ParseMultipartBody(char *src, char *bond); + +//From account.cpp +void WINAPI GetStatusFcn(CAccount *Which, wchar_t *Value); + +extern HYAMNPROTOPLUGIN POP3Plugin; + +//from decode.cpp +int DecodeQuotedPrintable(char *Src, char *Dst, int DstLen, BOOL isQ); +int DecodeBase64(char *Src, char *Dst, int DstLen); + +//From filterplugin.cpp +extern PYAMN_FILTERPLUGINQUEUE FirstFilterPlugin; + +//From protoplugin.cpp +extern PYAMN_PROTOPLUGINQUEUE FirstProtoPlugin; + +extern struct CExportedFunctions ProtoPluginExportedFcn[1]; +extern struct CExportedServices ProtoPluginExportedSvc[5]; +//From filterplugin.cpp +extern struct CExportedFunctions FilterPluginExportedFcn[1]; +extern struct CExportedServices FilterPluginExportedSvc[2]; +//From synchro.cpp +extern struct CExportedFunctions SynchroExportedFcn[7]; +//From account.cpp +extern struct CExportedFunctions AccountExportedFcn[2]; +extern struct CExportedServices AccountExportedSvc[9]; +//From mails.cpp (MIME) +extern struct CExportedFunctions MailExportedFcn[8]; +extern struct CExportedServices MailExportedSvc[5]; + +extern char *iconDescs[]; +extern char *iconNames[]; +extern HIMAGELIST CSImages; + +extern void __stdcall SSL_DebugLog(const char *fmt, ...); + +extern struct WndHandles *MessageWnd; + +extern int GetCharsetFromString(char *input, size_t size); +extern void ConvertCodedStringToUnicode(char *stream, wchar_t **storeto, uint32_t cp, int mode); +extern void __cdecl MailBrowser(void *Param); +extern void __cdecl BadConnection(void *Param); +extern PVOID TLSCtx; +extern PVOID SSLCtx; + +extern PYAMN_VARIABLES pYAMNVar; + +#endif diff --git a/protocols/YAMN/src/synchro.cpp b/protocols/YAMN/src/synchro.cpp index c4bb58e6de..184156787d 100644 --- a/protocols/YAMN/src/synchro.cpp +++ b/protocols/YAMN/src/synchro.cpp @@ -1,315 +1,315 @@ -/* - * This code implements synchronization objects code between threads. If you want, you can include it to your - * code. This file is not dependent on any other external code (functions) - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - - // Initializes a SWMRG structure. This structure must be - // initialized before any writer or reader threads attempt - // to wait on it. - // The structure must be allocated by the application and - // the structure's address is passed as the first parameter. - // The lpszName parameter is the name of the object. Pass - // NULL if you do not want to share the object. -BOOL WINAPI SWMRGInitialize(PSWMRG pSWMRG, wchar_t *Name); - -// Deletes the system resources associated with a SWMRG -// structure. The structure must be deleted only when -// no writer or reader threads in the calling process -// will wait on it. -void WINAPI SWMRGDelete(PSWMRG pSWMRG); - -// A writer thread calls this function to know when -// it can successfully write to the shared data. -// returns WAIT_FINISH when we are in write-access or WAIT_FAILED -// when event about quick finishing is set (or when system returns fail when waiting for synchro object) -uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout); - -// A writer thread calls this function to let other threads -// know that it no longer needs to write to the shared data. -void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG); - -// A reader thread calls this function to know when -// it can successfully read the shared data. -// returns WAIT_FINISH when we are in read-access or WAIT_FAILED -// when event about quick finishing is set (or when system returns fail when waiting for synchro object) -uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); - -// A reader thread calls this function to let other threads -// know when it no longer needs to read the shared data. -void WINAPI SWMRGDoneReading(PSWMRG pSWMRG); - -// WaitToReadFcn -// is used to wait for read access with SWMRG SO, but it also increments counter if successfull -// returns WAIT_FAILED or WAIT_FINISH -// when WAIT_FAILED, we should not begin to access datas, we are not in read-access mode -uint32_t WINAPI WaitToReadFcn(PSWMRG SObject); - -// WriteDoneFcn -// is used to release read access with SWMRG SO, but it also decrements counter if successfull -void WINAPI ReadDoneFcn(PSWMRG SObject); - -// This functions is for export purposes -// Plugin can call this function to manage SCOUNTER synchronization object - -// Gets number value stored in SCOUNTER SO -// Note you must not read the number from memory directly, because -// CPU can stop reading thread when it has read HI-Word, then another thread -// can change the value and then OS starts the previous thread, that reads the -// LO-uint16_t of uint32_t. And the return value HI+LO-uint16_t is corrupted -uint32_t WINAPI SCGetNumberFcn(PSCOUNTER SCounter); - -// Increments SCOUNTER and unsets event -// Returns Number after incrementing -uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter); - -// Decrements SCOUNTER and sets event if zero -// Returns Number after decrementing -uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter); - -struct CExportedFunctions SynchroExportedFcn[] = -{ - {YAMN_WAITTOWRITEID, (void *)WaitToWriteFcn}, - {YAMN_WRITEDONEID, (void *)WriteDoneFcn}, - {YAMN_WAITTOREADID, (void *)WaitToReadFcn}, - {YAMN_READDONEID, (void *)ReadDoneFcn}, - {YAMN_SCGETNUMBERID, (void *)SCGetNumberFcn}, - {YAMN_SCINCID, (void *)SCIncFcn}, - {YAMN_SCDECID, (void *)SCDecFcn}, -}; - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -void WINAPI SWMRGDelete(PSWMRG pSWMRG) -{ - // Destroys any synchronization objects that were - // successfully created. - if (nullptr != pSWMRG->hEventNoWriter) - CloseHandle(pSWMRG->hEventNoWriter); - if (nullptr != pSWMRG->hEventNoReaders) - CloseHandle(pSWMRG->hEventNoReaders); - if (nullptr != pSWMRG->hSemNumReaders) - CloseHandle(pSWMRG->hSemNumReaders); - if (nullptr != pSWMRG->hFinishEV) - CloseHandle(pSWMRG->hFinishEV); -} - -BOOL WINAPI SWMRGInitialize(PSWMRG pSWMRG, wchar_t *Name) -{ - pSWMRG->hEventNoWriter = nullptr; - pSWMRG->hEventNoReaders = nullptr; - pSWMRG->hSemNumReaders = nullptr; - pSWMRG->hFinishEV = nullptr; - - // Creates the automatic-reset event that is signalled when - // no writer threads are writing. - // Initially no reader threads are reading. - if (Name != nullptr) - Name[0] = (wchar_t)'W'; - pSWMRG->hEventNoWriter = CreateEvent(nullptr, FALSE, TRUE, Name); - - // Creates the manual-reset event that is signalled when - // no reader threads are reading. - // Initially no reader threads are reading. - if (Name != nullptr) - Name[0] = (wchar_t)'R'; - pSWMRG->hEventNoReaders = CreateEvent(nullptr, TRUE, TRUE, Name); - - // Initializes the variable that indicates the number of - // reader threads that are reading. - // Initially no reader threads are reading. - if (Name != nullptr) - Name[0] = (wchar_t)'C'; - pSWMRG->hSemNumReaders = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, Name); - - if (Name != nullptr) - Name[0] = (wchar_t)'F'; - pSWMRG->hFinishEV = CreateEvent(nullptr, TRUE, FALSE, Name); - - // If a synchronization object could not be created, - // destroys any created objects and return failure. - if ((nullptr == pSWMRG->hEventNoWriter) || (nullptr == pSWMRG->hEventNoReaders) || (nullptr == pSWMRG->hSemNumReaders) || (nullptr == pSWMRG->hFinishEV)) { - SWMRGDelete(pSWMRG); - return FALSE; - } - return TRUE; -} - -uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout) -{ - uint32_t dw; - HANDLE aHandles[2]; - - // We can write if the following are true: - // 1. No other threads are writing. - // 2. No threads are reading. - // But first we have to know if SWMRG structure is not about to delete - aHandles[0] = pSWMRG->hEventNoWriter; - aHandles[1] = pSWMRG->hEventNoReaders; - if (WAIT_OBJECT_0 == (dw = WaitForSingleObject(pSWMRG->hFinishEV, 0))) - return WAIT_FINISH; - if (WAIT_FAILED == dw) - return dw; - dw = WaitForMultipleObjects(2, aHandles, TRUE, dwTimeout); - // if a request to delete became later, we should not catch it. Try once more to ask if account is not about to delete - if ((dw != WAIT_FAILED) && (WAIT_OBJECT_0 == (WaitForSingleObject(pSWMRG->hFinishEV, 0)))) { - SetEvent(pSWMRG->hEventNoWriter); - return WAIT_FINISH; - } - - // This thread can write to the shared data. - // Automatic event for NoWriter sets hEventNoWriter to nonsignaled after WaitForMultipleObject - - // Because a writer thread is writing, the Event - // should not be reset. This stops other - // writers and readers. - return dw; -} - -void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG) -// Presumably, a writer thread calling this function has -// successfully called WaitToWrite. This means that we -// do not have to wait on any synchronization objects -// here because the writer already owns the Event. -{ - // Allow other writer/reader threads to use - // the SWMRG synchronization object. - SetEvent(pSWMRG->hEventNoWriter); -} - -uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout) -{ - uint32_t dw; - LONG lPreviousCount; - - // We can read if no threads are writing. - // And there's not request to delete structure - if (WAIT_OBJECT_0 == (dw = WaitForSingleObject(pSWMRG->hFinishEV, 0))) - return WAIT_FINISH; - if (WAIT_FAILED == dw) - return dw; - dw = WaitForSingleObject(pSWMRG->hEventNoWriter, dwTimeout); - // if a request to delete became later, we should not catch it. Try once more to ask if account is not about to delete - if ((dw != WAIT_FAILED) && (WAIT_OBJECT_0 == (WaitForSingleObject(pSWMRG->hFinishEV, 0)))) { - SetEvent(pSWMRG->hEventNoWriter); - return WAIT_FINISH; - } - - if (dw == WAIT_OBJECT_0) { - // This thread can read from the shared data. - // Increment the number of reader threads. - // But there can't be more than one thread incrementing readers, - // so this is why we use semaphore. - ReleaseSemaphore(pSWMRG->hSemNumReaders, 1, &lPreviousCount); - if (lPreviousCount == 0) - // If this is the first reader thread, - // set event to reflect this. Other reader threads can read, no writer thread can write. - ResetEvent(pSWMRG->hEventNoReaders); - - // Allow other writer/reader threads to use - // the SWMRG synchronization object. hEventNoWrite is still non-signaled - // (it looks like writer is processing thread, but it is not true) - SetEvent(pSWMRG->hEventNoWriter); - } - - return(dw); -} - -void WINAPI SWMRGDoneReading(PSWMRG pSWMRG) -{ - HANDLE aHandles[2]; - LONG lNumReaders; - - // We can stop reading if the events are available, - // but when we stop reading we must also decrement the - // number of reader threads. - aHandles[0] = pSWMRG->hEventNoWriter; - aHandles[1] = pSWMRG->hSemNumReaders; - WaitForMultipleObjects(2, aHandles, TRUE, INFINITE); - - // Get the remaining number of readers by releasing the - // semaphore and then restoring the count by immediately - // performing a wait. - ReleaseSemaphore(pSWMRG->hSemNumReaders, 1, &lNumReaders); - WaitForSingleObject(pSWMRG->hSemNumReaders, INFINITE); - - // If there are no remaining readers, - // set the event to relect this. - if (lNumReaders == 0) - // If there are no reader threads, - // set our event to reflect this. - SetEvent(pSWMRG->hEventNoReaders); - - // Allow other writer/reader threads to use - // the SWMRG synchronization object. - // (it looks like writer is processing thread, but it is not true) - SetEvent(pSWMRG->hEventNoWriter); -} - -uint32_t WINAPI WaitToWriteFcn(PSWMRG SObject, PSCOUNTER SCounter) -{ - uint32_t EnterCode; - - if (WAIT_OBJECT_0 == (EnterCode = SWMRGWaitToWrite(SObject, INFINITE))) - if (SCounter != nullptr) - SCIncFcn(SCounter); - - return EnterCode; -} - -void WINAPI WriteDoneFcn(PSWMRG SObject, PSCOUNTER SCounter) -{ - SWMRGDoneWriting(SObject); - if (SCounter != nullptr) - SCDecFcn(SCounter); -} - -uint32_t WINAPI WaitToReadFcn(PSWMRG SObject) -{ - uint32_t EnterCode = SWMRGWaitToRead(SObject, INFINITE); - return EnterCode; -} - -void WINAPI ReadDoneFcn(PSWMRG SObject) -{ - SWMRGDoneReading(SObject); -} - -uint32_t WINAPI SCGetNumberFcn(PSCOUNTER SCounter) -{ - - EnterCriticalSection(&SCounter->CounterCS); - - uint32_t Temp = SCounter->Number; - - LeaveCriticalSection(&SCounter->CounterCS); - return Temp; -} - -uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter) -{ - EnterCriticalSection(&SCounter->CounterCS); - - uint32_t Temp = ++SCounter->Number; - ResetEvent(SCounter->Event); - - LeaveCriticalSection(&SCounter->CounterCS); - return Temp; -} - -uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter) -{ - uint32_t Temp; - EnterCriticalSection(&SCounter->CounterCS); - - if (!(Temp = --SCounter->Number)) { - SetEvent(SCounter->Event); - } - - LeaveCriticalSection(&SCounter->CounterCS); - return Temp; -} +/* + * This code implements synchronization objects code between threads. If you want, you can include it to your + * code. This file is not dependent on any other external code (functions) + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + + // Initializes a SWMRG structure. This structure must be + // initialized before any writer or reader threads attempt + // to wait on it. + // The structure must be allocated by the application and + // the structure's address is passed as the first parameter. + // The lpszName parameter is the name of the object. Pass + // NULL if you do not want to share the object. +BOOL WINAPI SWMRGInitialize(PSWMRG pSWMRG, wchar_t *Name); + +// Deletes the system resources associated with a SWMRG +// structure. The structure must be deleted only when +// no writer or reader threads in the calling process +// will wait on it. +void WINAPI SWMRGDelete(PSWMRG pSWMRG); + +// A writer thread calls this function to know when +// it can successfully write to the shared data. +// returns WAIT_FINISH when we are in write-access or WAIT_FAILED +// when event about quick finishing is set (or when system returns fail when waiting for synchro object) +uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout); + +// A writer thread calls this function to let other threads +// know that it no longer needs to write to the shared data. +void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG); + +// A reader thread calls this function to know when +// it can successfully read the shared data. +// returns WAIT_FINISH when we are in read-access or WAIT_FAILED +// when event about quick finishing is set (or when system returns fail when waiting for synchro object) +uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout); + +// A reader thread calls this function to let other threads +// know when it no longer needs to read the shared data. +void WINAPI SWMRGDoneReading(PSWMRG pSWMRG); + +// WaitToReadFcn +// is used to wait for read access with SWMRG SO, but it also increments counter if successfull +// returns WAIT_FAILED or WAIT_FINISH +// when WAIT_FAILED, we should not begin to access datas, we are not in read-access mode +uint32_t WINAPI WaitToReadFcn(PSWMRG SObject); + +// WriteDoneFcn +// is used to release read access with SWMRG SO, but it also decrements counter if successfull +void WINAPI ReadDoneFcn(PSWMRG SObject); + +// This functions is for export purposes +// Plugin can call this function to manage SCOUNTER synchronization object + +// Gets number value stored in SCOUNTER SO +// Note you must not read the number from memory directly, because +// CPU can stop reading thread when it has read HI-Word, then another thread +// can change the value and then OS starts the previous thread, that reads the +// LO-uint16_t of uint32_t. And the return value HI+LO-uint16_t is corrupted +uint32_t WINAPI SCGetNumberFcn(PSCOUNTER SCounter); + +// Increments SCOUNTER and unsets event +// Returns Number after incrementing +uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter); + +// Decrements SCOUNTER and sets event if zero +// Returns Number after decrementing +uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter); + +struct CExportedFunctions SynchroExportedFcn[] = +{ + {YAMN_WAITTOWRITEID, (void *)WaitToWriteFcn}, + {YAMN_WRITEDONEID, (void *)WriteDoneFcn}, + {YAMN_WAITTOREADID, (void *)WaitToReadFcn}, + {YAMN_READDONEID, (void *)ReadDoneFcn}, + {YAMN_SCGETNUMBERID, (void *)SCGetNumberFcn}, + {YAMN_SCINCID, (void *)SCIncFcn}, + {YAMN_SCDECID, (void *)SCDecFcn}, +}; + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +void WINAPI SWMRGDelete(PSWMRG pSWMRG) +{ + // Destroys any synchronization objects that were + // successfully created. + if (nullptr != pSWMRG->hEventNoWriter) + CloseHandle(pSWMRG->hEventNoWriter); + if (nullptr != pSWMRG->hEventNoReaders) + CloseHandle(pSWMRG->hEventNoReaders); + if (nullptr != pSWMRG->hSemNumReaders) + CloseHandle(pSWMRG->hSemNumReaders); + if (nullptr != pSWMRG->hFinishEV) + CloseHandle(pSWMRG->hFinishEV); +} + +BOOL WINAPI SWMRGInitialize(PSWMRG pSWMRG, wchar_t *Name) +{ + pSWMRG->hEventNoWriter = nullptr; + pSWMRG->hEventNoReaders = nullptr; + pSWMRG->hSemNumReaders = nullptr; + pSWMRG->hFinishEV = nullptr; + + // Creates the automatic-reset event that is signalled when + // no writer threads are writing. + // Initially no reader threads are reading. + if (Name != nullptr) + Name[0] = (wchar_t)'W'; + pSWMRG->hEventNoWriter = CreateEvent(nullptr, FALSE, TRUE, Name); + + // Creates the manual-reset event that is signalled when + // no reader threads are reading. + // Initially no reader threads are reading. + if (Name != nullptr) + Name[0] = (wchar_t)'R'; + pSWMRG->hEventNoReaders = CreateEvent(nullptr, TRUE, TRUE, Name); + + // Initializes the variable that indicates the number of + // reader threads that are reading. + // Initially no reader threads are reading. + if (Name != nullptr) + Name[0] = (wchar_t)'C'; + pSWMRG->hSemNumReaders = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, Name); + + if (Name != nullptr) + Name[0] = (wchar_t)'F'; + pSWMRG->hFinishEV = CreateEvent(nullptr, TRUE, FALSE, Name); + + // If a synchronization object could not be created, + // destroys any created objects and return failure. + if ((nullptr == pSWMRG->hEventNoWriter) || (nullptr == pSWMRG->hEventNoReaders) || (nullptr == pSWMRG->hSemNumReaders) || (nullptr == pSWMRG->hFinishEV)) { + SWMRGDelete(pSWMRG); + return FALSE; + } + return TRUE; +} + +uint32_t WINAPI SWMRGWaitToWrite(PSWMRG pSWMRG, uint32_t dwTimeout) +{ + uint32_t dw; + HANDLE aHandles[2]; + + // We can write if the following are true: + // 1. No other threads are writing. + // 2. No threads are reading. + // But first we have to know if SWMRG structure is not about to delete + aHandles[0] = pSWMRG->hEventNoWriter; + aHandles[1] = pSWMRG->hEventNoReaders; + if (WAIT_OBJECT_0 == (dw = WaitForSingleObject(pSWMRG->hFinishEV, 0))) + return WAIT_FINISH; + if (WAIT_FAILED == dw) + return dw; + dw = WaitForMultipleObjects(2, aHandles, TRUE, dwTimeout); + // if a request to delete became later, we should not catch it. Try once more to ask if account is not about to delete + if ((dw != WAIT_FAILED) && (WAIT_OBJECT_0 == (WaitForSingleObject(pSWMRG->hFinishEV, 0)))) { + SetEvent(pSWMRG->hEventNoWriter); + return WAIT_FINISH; + } + + // This thread can write to the shared data. + // Automatic event for NoWriter sets hEventNoWriter to nonsignaled after WaitForMultipleObject + + // Because a writer thread is writing, the Event + // should not be reset. This stops other + // writers and readers. + return dw; +} + +void WINAPI SWMRGDoneWriting(PSWMRG pSWMRG) +// Presumably, a writer thread calling this function has +// successfully called WaitToWrite. This means that we +// do not have to wait on any synchronization objects +// here because the writer already owns the Event. +{ + // Allow other writer/reader threads to use + // the SWMRG synchronization object. + SetEvent(pSWMRG->hEventNoWriter); +} + +uint32_t WINAPI SWMRGWaitToRead(PSWMRG pSWMRG, uint32_t dwTimeout) +{ + uint32_t dw; + LONG lPreviousCount; + + // We can read if no threads are writing. + // And there's not request to delete structure + if (WAIT_OBJECT_0 == (dw = WaitForSingleObject(pSWMRG->hFinishEV, 0))) + return WAIT_FINISH; + if (WAIT_FAILED == dw) + return dw; + dw = WaitForSingleObject(pSWMRG->hEventNoWriter, dwTimeout); + // if a request to delete became later, we should not catch it. Try once more to ask if account is not about to delete + if ((dw != WAIT_FAILED) && (WAIT_OBJECT_0 == (WaitForSingleObject(pSWMRG->hFinishEV, 0)))) { + SetEvent(pSWMRG->hEventNoWriter); + return WAIT_FINISH; + } + + if (dw == WAIT_OBJECT_0) { + // This thread can read from the shared data. + // Increment the number of reader threads. + // But there can't be more than one thread incrementing readers, + // so this is why we use semaphore. + ReleaseSemaphore(pSWMRG->hSemNumReaders, 1, &lPreviousCount); + if (lPreviousCount == 0) + // If this is the first reader thread, + // set event to reflect this. Other reader threads can read, no writer thread can write. + ResetEvent(pSWMRG->hEventNoReaders); + + // Allow other writer/reader threads to use + // the SWMRG synchronization object. hEventNoWrite is still non-signaled + // (it looks like writer is processing thread, but it is not true) + SetEvent(pSWMRG->hEventNoWriter); + } + + return(dw); +} + +void WINAPI SWMRGDoneReading(PSWMRG pSWMRG) +{ + HANDLE aHandles[2]; + LONG lNumReaders; + + // We can stop reading if the events are available, + // but when we stop reading we must also decrement the + // number of reader threads. + aHandles[0] = pSWMRG->hEventNoWriter; + aHandles[1] = pSWMRG->hSemNumReaders; + WaitForMultipleObjects(2, aHandles, TRUE, INFINITE); + + // Get the remaining number of readers by releasing the + // semaphore and then restoring the count by immediately + // performing a wait. + ReleaseSemaphore(pSWMRG->hSemNumReaders, 1, &lNumReaders); + WaitForSingleObject(pSWMRG->hSemNumReaders, INFINITE); + + // If there are no remaining readers, + // set the event to relect this. + if (lNumReaders == 0) + // If there are no reader threads, + // set our event to reflect this. + SetEvent(pSWMRG->hEventNoReaders); + + // Allow other writer/reader threads to use + // the SWMRG synchronization object. + // (it looks like writer is processing thread, but it is not true) + SetEvent(pSWMRG->hEventNoWriter); +} + +uint32_t WINAPI WaitToWriteFcn(PSWMRG SObject, PSCOUNTER SCounter) +{ + uint32_t EnterCode; + + if (WAIT_OBJECT_0 == (EnterCode = SWMRGWaitToWrite(SObject, INFINITE))) + if (SCounter != nullptr) + SCIncFcn(SCounter); + + return EnterCode; +} + +void WINAPI WriteDoneFcn(PSWMRG SObject, PSCOUNTER SCounter) +{ + SWMRGDoneWriting(SObject); + if (SCounter != nullptr) + SCDecFcn(SCounter); +} + +uint32_t WINAPI WaitToReadFcn(PSWMRG SObject) +{ + uint32_t EnterCode = SWMRGWaitToRead(SObject, INFINITE); + return EnterCode; +} + +void WINAPI ReadDoneFcn(PSWMRG SObject) +{ + SWMRGDoneReading(SObject); +} + +uint32_t WINAPI SCGetNumberFcn(PSCOUNTER SCounter) +{ + + EnterCriticalSection(&SCounter->CounterCS); + + uint32_t Temp = SCounter->Number; + + LeaveCriticalSection(&SCounter->CounterCS); + return Temp; +} + +uint32_t WINAPI SCIncFcn(PSCOUNTER SCounter) +{ + EnterCriticalSection(&SCounter->CounterCS); + + uint32_t Temp = ++SCounter->Number; + ResetEvent(SCounter->Event); + + LeaveCriticalSection(&SCounter->CounterCS); + return Temp; +} + +uint32_t WINAPI SCDecFcn(PSCOUNTER SCounter) +{ + uint32_t Temp; + EnterCriticalSection(&SCounter->CounterCS); + + if (!(Temp = --SCounter->Number)) { + SetEvent(SCounter->Event); + } + + LeaveCriticalSection(&SCounter->CounterCS); + return Temp; +} diff --git a/protocols/YAMN/src/yamn.cpp b/protocols/YAMN/src/yamn.cpp index d0d80021f8..2af7efd74f 100644 --- a/protocols/YAMN/src/yamn.cpp +++ b/protocols/YAMN/src/yamn.cpp @@ -1,230 +1,230 @@ -/* - * This code implements miscellaneous usefull functions - * - * (c) majvan 2002-2004 - */ - -#include "stdafx.h" - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -//Plugin registration CS -//Used if we add (register) plugin to YAMN plugins and when we browse through registered plugins -mir_cs PluginRegCS; - -//AccountWriterCS -//We want to store number of writers of Accounts (number of Accounts used for writing) -//If we want to read all accounts (for saving to file) immidiatelly, we have to wait until no account is changing (no thread writing to account) -SCOUNTER *AccountWriterSO; - -//NoExitEV -//Event that is signaled when there's a request to exit, so no new pop3 check should be performed -HANDLE ExitEV; - -//WriteToFileEV -//If this is signaled, write accounts to file is performed. Set this event if you want to actualize your accounts and messages -HANDLE WriteToFileEV; - -//Returns pointer to YAMN exported function -INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM lParam); - -//Returns pointer to YAMN variables -INT_PTR GetVariablesSvc(WPARAM wParam, LPARAM); - -// Function every seconds decrements account counter of seconds and checks if they are 0 -// If yes, creates a POP3 thread to check account -void CALLBACK TimerProc(HWND, UINT, UINT, uint32_t); - -// Function called to check all accounts immidialtelly -// no params -INT_PTR ForceCheckSvc(WPARAM, LPARAM); - -//thread is running all the time -//waits for WriteToFileEV and then writes all accounts to file -//uint32_t WINAPI FileWritingThread(PVOID); - -// Function is called when Miranda notifies plugin that it is about to exit -// Ensures succesfull end of POP3 checking, sets event that no next checking should be performed -// If there's no writer to account (POP3 thread), saves the results to the file -//not used now, perhaps in the future - - -//int ExitProc(WPARAM wParam, LPARAM lParam); - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM) -{ - int i; - - for (i=0;iNext) { - if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualPlugin->Plugin->AccountBrowserSO, 0)) // we want to access accounts immiadtelly - return; - - for (ActualAccount = ActualPlugin->Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { - if (ActualAccount->Plugin == nullptr || ActualAccount->Plugin->Fcn == nullptr) //account not inited - continue; - - if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualAccount->AccountAccessSO, 0)) - continue; - - BOOL isAccountCounting = 0; - if ((ActualAccount->Flags & YAMN_ACC_ENA) && - (((ActualAccount->StatusFlags & YAMN_ACC_ST0) && (Status <= ID_STATUS_OFFLINE)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST1) && (Status == ID_STATUS_ONLINE)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST2) && (Status == ID_STATUS_AWAY)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST3) && (Status == ID_STATUS_DND)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST4) && (Status == ID_STATUS_NA)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST5) && (Status == ID_STATUS_OCCUPIED)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST6) && (Status == ID_STATUS_FREECHAT)) || - ((ActualAccount->StatusFlags & YAMN_ACC_ST7) && (Status == ID_STATUS_INVISIBLE)))) - { - if ((!ActualAccount->Interval && !ActualAccount->TimeLeft) || ActualAccount->Plugin->Fcn->TimeoutFcnPtr == nullptr) - goto ChangeIsCountingStatusLabel; - - if (ActualAccount->TimeLeft) { - ActualAccount->TimeLeft--; - isAccountCounting = TRUE; - } - - WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGETIME, (WPARAM)ActualAccount, (LPARAM)ActualAccount->TimeLeft); - if (!ActualAccount->TimeLeft) { - struct CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_NORMALCHECK, (void *)nullptr, nullptr}; - - ActualAccount->TimeLeft = ActualAccount->Interval; - HANDLE NewThread = CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->TimeoutFcnPtr, &ParamToPlugin, 0, &tid); - if (NewThread == nullptr) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - continue; - } - else { - WaitForSingleObject(ThreadRunningEV, INFINITE); - CloseHandle(NewThread); - } - } - } - -ChangeIsCountingStatusLabel: - if (((ActualAccount->isCounting) != 0) != isAccountCounting) { - ActualAccount->isCounting = isAccountCounting; - uint16_t cStatus = g_plugin.getWord(ActualAccount->hContact, "Status"); - switch (cStatus) { - case ID_STATUS_ONLINE: - case ID_STATUS_OFFLINE: - g_plugin.setWord(ActualAccount->hContact, "Status", isAccountCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); - default: - break; - } - } - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - SWMRGDoneReading(ActualPlugin->Plugin->AccountBrowserSO); - } - CloseHandle(ThreadRunningEV); -} - -INT_PTR ForceCheckSvc(WPARAM, LPARAM) -{ - CAccount *ActualAccount; - DWORD tid; - - //we use event to signal, that running thread has all needed stack parameters copied - HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (ThreadRunningEV == nullptr) - return 0; - //if we want to close miranda, we get event and do not run pop3 checking anymore - if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) - return 0; - - { - mir_cslock lck(PluginRegCS); - for (PYAMN_PROTOPLUGINQUEUE ActualPlugin = FirstProtoPlugin; ActualPlugin != nullptr; ActualPlugin = ActualPlugin->Next) { - SWMRGWaitToRead(ActualPlugin->Plugin->AccountBrowserSO, INFINITE); - for (ActualAccount = ActualPlugin->Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { - if (ActualAccount->Plugin->Fcn == nullptr) //account not inited - continue; - - if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) - continue; - - if ((ActualAccount->Flags & YAMN_ACC_ENA) && (ActualAccount->StatusFlags & YAMN_ACC_FORCE)) { //account cannot be forced to check - if (ActualAccount->Plugin->Fcn->ForceCheckFcnPtr == nullptr) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - continue; - } - struct CheckParam ParamToPlugin = { YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_FORCECHECK, (void *)nullptr, nullptr }; - - if (nullptr == CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->ForceCheckFcnPtr, &ParamToPlugin, 0, &tid)) { - ReadDoneFcn(ActualAccount->AccountAccessSO); - continue; - } - else - WaitForSingleObject(ThreadRunningEV, INFINITE); - } - ReadDoneFcn(ActualAccount->AccountAccessSO); - } - SWMRGDoneReading(ActualPlugin->Plugin->AccountBrowserSO); - } - } - - CloseHandle(ThreadRunningEV); - - if (hTTButton) - CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTTButton, 0); - return 1; -} +/* + * This code implements miscellaneous usefull functions + * + * (c) majvan 2002-2004 + */ + +#include "stdafx.h" + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +//Plugin registration CS +//Used if we add (register) plugin to YAMN plugins and when we browse through registered plugins +mir_cs PluginRegCS; + +//AccountWriterCS +//We want to store number of writers of Accounts (number of Accounts used for writing) +//If we want to read all accounts (for saving to file) immidiatelly, we have to wait until no account is changing (no thread writing to account) +SCOUNTER *AccountWriterSO; + +//NoExitEV +//Event that is signaled when there's a request to exit, so no new pop3 check should be performed +HANDLE ExitEV; + +//WriteToFileEV +//If this is signaled, write accounts to file is performed. Set this event if you want to actualize your accounts and messages +HANDLE WriteToFileEV; + +//Returns pointer to YAMN exported function +INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM lParam); + +//Returns pointer to YAMN variables +INT_PTR GetVariablesSvc(WPARAM wParam, LPARAM); + +// Function every seconds decrements account counter of seconds and checks if they are 0 +// If yes, creates a POP3 thread to check account +void CALLBACK TimerProc(HWND, UINT, UINT, uint32_t); + +// Function called to check all accounts immidialtelly +// no params +INT_PTR ForceCheckSvc(WPARAM, LPARAM); + +//thread is running all the time +//waits for WriteToFileEV and then writes all accounts to file +//uint32_t WINAPI FileWritingThread(PVOID); + +// Function is called when Miranda notifies plugin that it is about to exit +// Ensures succesfull end of POP3 checking, sets event that no next checking should be performed +// If there's no writer to account (POP3 thread), saves the results to the file +//not used now, perhaps in the future + + +//int ExitProc(WPARAM wParam, LPARAM lParam); + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- + +INT_PTR GetFcnPtrSvc(WPARAM wParam, LPARAM) +{ + int i; + + for (i=0;iNext) { + if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualPlugin->Plugin->AccountBrowserSO, 0)) // we want to access accounts immiadtelly + return; + + for (ActualAccount = ActualPlugin->Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { + if (ActualAccount->Plugin == nullptr || ActualAccount->Plugin->Fcn == nullptr) //account not inited + continue; + + if (WAIT_OBJECT_0 != SWMRGWaitToRead(ActualAccount->AccountAccessSO, 0)) + continue; + + BOOL isAccountCounting = 0; + if ((ActualAccount->Flags & YAMN_ACC_ENA) && + (((ActualAccount->StatusFlags & YAMN_ACC_ST0) && (Status <= ID_STATUS_OFFLINE)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST1) && (Status == ID_STATUS_ONLINE)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST2) && (Status == ID_STATUS_AWAY)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST3) && (Status == ID_STATUS_DND)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST4) && (Status == ID_STATUS_NA)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST5) && (Status == ID_STATUS_OCCUPIED)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST6) && (Status == ID_STATUS_FREECHAT)) || + ((ActualAccount->StatusFlags & YAMN_ACC_ST7) && (Status == ID_STATUS_INVISIBLE)))) + { + if ((!ActualAccount->Interval && !ActualAccount->TimeLeft) || ActualAccount->Plugin->Fcn->TimeoutFcnPtr == nullptr) + goto ChangeIsCountingStatusLabel; + + if (ActualAccount->TimeLeft) { + ActualAccount->TimeLeft--; + isAccountCounting = TRUE; + } + + WindowList_BroadcastAsync(YAMNVar.MessageWnds, WM_YAMN_CHANGETIME, (WPARAM)ActualAccount, (LPARAM)ActualAccount->TimeLeft); + if (!ActualAccount->TimeLeft) { + struct CheckParam ParamToPlugin = {YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_NORMALCHECK, (void *)nullptr, nullptr}; + + ActualAccount->TimeLeft = ActualAccount->Interval; + HANDLE NewThread = CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->TimeoutFcnPtr, &ParamToPlugin, 0, &tid); + if (NewThread == nullptr) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + continue; + } + else { + WaitForSingleObject(ThreadRunningEV, INFINITE); + CloseHandle(NewThread); + } + } + } + +ChangeIsCountingStatusLabel: + if (((ActualAccount->isCounting) != 0) != isAccountCounting) { + ActualAccount->isCounting = isAccountCounting; + uint16_t cStatus = g_plugin.getWord(ActualAccount->hContact, "Status"); + switch (cStatus) { + case ID_STATUS_ONLINE: + case ID_STATUS_OFFLINE: + g_plugin.setWord(ActualAccount->hContact, "Status", isAccountCounting ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); + default: + break; + } + } + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + SWMRGDoneReading(ActualPlugin->Plugin->AccountBrowserSO); + } + CloseHandle(ThreadRunningEV); +} + +INT_PTR ForceCheckSvc(WPARAM, LPARAM) +{ + CAccount *ActualAccount; + DWORD tid; + + //we use event to signal, that running thread has all needed stack parameters copied + HANDLE ThreadRunningEV = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (ThreadRunningEV == nullptr) + return 0; + //if we want to close miranda, we get event and do not run pop3 checking anymore + if (WAIT_OBJECT_0 == WaitForSingleObject(ExitEV, 0)) + return 0; + + { + mir_cslock lck(PluginRegCS); + for (PYAMN_PROTOPLUGINQUEUE ActualPlugin = FirstProtoPlugin; ActualPlugin != nullptr; ActualPlugin = ActualPlugin->Next) { + SWMRGWaitToRead(ActualPlugin->Plugin->AccountBrowserSO, INFINITE); + for (ActualAccount = ActualPlugin->Plugin->FirstAccount; ActualAccount != nullptr; ActualAccount = ActualAccount->Next) { + if (ActualAccount->Plugin->Fcn == nullptr) //account not inited + continue; + + if (WAIT_OBJECT_0 != WaitToReadFcn(ActualAccount->AccountAccessSO)) + continue; + + if ((ActualAccount->Flags & YAMN_ACC_ENA) && (ActualAccount->StatusFlags & YAMN_ACC_FORCE)) { //account cannot be forced to check + if (ActualAccount->Plugin->Fcn->ForceCheckFcnPtr == nullptr) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + continue; + } + struct CheckParam ParamToPlugin = { YAMN_CHECKVERSION, ThreadRunningEV, ActualAccount, YAMN_FORCECHECK, (void *)nullptr, nullptr }; + + if (nullptr == CreateThread(nullptr, 0, (YAMN_STANDARDFCN)ActualAccount->Plugin->Fcn->ForceCheckFcnPtr, &ParamToPlugin, 0, &tid)) { + ReadDoneFcn(ActualAccount->AccountAccessSO); + continue; + } + else + WaitForSingleObject(ThreadRunningEV, INFINITE); + } + ReadDoneFcn(ActualAccount->AccountAccessSO); + } + SWMRGDoneReading(ActualPlugin->Plugin->AccountBrowserSO); + } + } + + CloseHandle(ThreadRunningEV); + + if (hTTButton) + CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTTButton, 0); + return 1; +} -- cgit v1.2.3