diff options
29 files changed, 1998 insertions, 0 deletions
diff --git a/protocols/Slack/Slack.vcxproj b/protocols/Slack/Slack.vcxproj new file mode 100644 index 0000000000..175062273a --- /dev/null +++ b/protocols/Slack/Slack.vcxproj @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{5487475F-00C2-4D88-AE41-C5969260D455}</ProjectGuid> + <ProjectName>Slack</ProjectName> + </PropertyGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" /> + </ImportGroup> + <ItemDefinitionGroup> + <ClCompile> + <AdditionalIncludeDirectories>include;..\..\include;..\..\plugins\ExternalAPI;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="src\api\*.h" /> + <None Include="res\*.ico" /> + </ItemGroup> +</Project> diff --git a/protocols/Slack/Slack.vcxproj.filters b/protocols/Slack/Slack.vcxproj.filters new file mode 100644 index 0000000000..2d048d8f94 --- /dev/null +++ b/protocols/Slack/Slack.vcxproj.filters @@ -0,0 +1,232 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" /> + <ItemGroup> + <ClCompile Include="src\stdafx.cxx"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\slack_accounts.cpp" /> + <ClCompile Include="src\slack_contacts.cpp" /> + <ClCompile Include="src\slack_events.cpp" /> + <ClCompile Include="src\slack_icons.cpp" /> + <ClCompile Include="src\slack_menus.cpp" /> + <ClCompile Include="src\slack_messages.cpp" /> + <ClCompile Include="src\slack_netlib.cpp" /> + <ClCompile Include="src\slack_options.cpp" /> + <ClCompile Include="src\slack_proto.cpp" /> + <ClCompile Include="src\slack_utils.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + <ClInclude Include="src\resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\slack_dialogs.h" /> + <ClInclude Include="src\slack_menus.h" /> + <ClInclude Include="src\slack_options.h" /> + <ClInclude Include="src\slack_proto.h" /> + <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\version.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="res\resource.rc"> + <Filter>Resource Files</Filter> + </ResourceCompile> + <ResourceCompile Include="res\version.rc" /> + <ResourceCompile Include="res\resource.rc"> + <Filter>Resource Files</Filter> + </ResourceCompile> + <ResourceCompile Include="res\version.rc" /> + </ItemGroup> + <ItemGroup> + <Image Include="res\slack.ico"> + <Filter>Resource Files</Filter> + </Image> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/protocols/Slack/res/resource.rc b/protocols/Slack/res/resource.rc new file mode 100644 index 0000000000..bdb7a3a004 --- /dev/null +++ b/protocols/Slack/res/resource.rc @@ -0,0 +1,131 @@ +// 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 resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SLACK ICON "slack.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OAUTH DIALOGEX 0, 0, 193, 83 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST +CAPTION "Authorization" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_OAUTH_CODE,7,41,179,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,75,62,50,14 + PUSHBUTTON "Cancel",IDCANCEL,136,62,50,14 + LTEXT "Enter authorization code:",IDC_STATIC,7,31,179,8 + LTEXT "Allow access to team",IDC_STATIC,7,7,68,8 + CONTROL "Go to this link",IDC_OAUTH_AUTHORIZE,"Hyperlink",WS_GROUP | WS_TABSTOP | 0x1,7,17,179,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OAUTH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 186 + TOPMARGIN, 7 + BOTTOMMARGIN, 76 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OAUTH AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Slack/res/slack.ico b/protocols/Slack/res/slack.ico Binary files differnew file mode 100644 index 0000000000..736b286cbb --- /dev/null +++ b/protocols/Slack/res/slack.ico diff --git a/protocols/Slack/res/version.rc b/protocols/Slack/res/version.rc new file mode 100644 index 0000000000..675d852de7 --- /dev/null +++ b/protocols/Slack/res/version.rc @@ -0,0 +1,38 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "afxres.h" +#include "..\src\version.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "FileDescription", __DESCRIPTION + VALUE "InternalName", __PLUGIN_NAME + VALUE "LegalCopyright", __COPYRIGHT + VALUE "OriginalFilename", __FILENAME + VALUE "ProductName", __PLUGIN_NAME + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/protocols/Slack/src/api/api_users.h b/protocols/Slack/src/api/api_users.h new file mode 100644 index 0000000000..5037e97374 --- /dev/null +++ b/protocols/Slack/src/api/api_users.h @@ -0,0 +1,18 @@ +#ifndef _SLACK_API_USERS_H_ +#define _SLACK_API_USERS_H_ + +class GetUserListRequest : public HttpRequest +{ +public: + GetUserListRequest(const char *token) : + HttpRequest(HttpMethod::HttpPost, SLACK_API_URL "/users.list") + { + Headers + << CHAR_VALUE("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); + Content + << CHAR_VALUE("token", token) + << CHAR_VALUE("presence", "true"); + } +}; + +#endif //_SLACK_API_USERS_H_
\ No newline at end of file diff --git a/protocols/Slack/src/http_request.h b/protocols/Slack/src/http_request.h new file mode 100644 index 0000000000..03f5894c6f --- /dev/null +++ b/protocols/Slack/src/http_request.h @@ -0,0 +1,347 @@ +#ifndef _HTTP_REQUEST_H_ +#define _HTTP_REQUEST_H_ + +typedef void (CSlackProto::*HttpCallback)(NETLIBHTTPREQUEST*, struct AsyncHttpRequest*); + +class HttpRequest; +class HttpResponse; + +struct VALUE +{ + LPCSTR szName; + __forceinline VALUE(LPCSTR _name) : szName(_name) { } +}; + +struct INT_VALUE : public VALUE +{ + int iValue; + __forceinline INT_VALUE(LPCSTR _name, int _value) + : VALUE(_name), iValue(_value) { } +}; + +struct LONG_VALUE : public VALUE +{ + LONGLONG llValue; + __forceinline LONG_VALUE(LPCSTR _name, LONGLONG _value) + : VALUE(_name), llValue(_value) { } +}; + +struct CHAR_VALUE : public VALUE +{ + LPCSTR szValue; + __forceinline CHAR_VALUE(LPCSTR _name, LPCSTR _value) + : VALUE(_name), szValue(_value) { } +}; + +struct ENCODED_VALUE : public VALUE +{ + LPSTR szValue; + __forceinline ENCODED_VALUE(LPCSTR _name, LPCSTR _value) + : VALUE(_name) { szValue = mir_urlEncode(_value); } + __forceinline ~ENCODED_VALUE() { mir_free(szValue); } +}; + +class HttpUri +{ + friend class HttpRequest; + +private: + CMStringA m_uri; + NETLIBHTTPREQUEST *m_request; + + HttpUri(NETLIBHTTPREQUEST *request, const char *uri) + : m_request(request), m_uri(uri) + { + if (m_request) + m_request->szUrl = m_uri.GetBuffer(); + } + + HttpUri(NETLIBHTTPREQUEST *request, const char *urlFormat, va_list args) + : m_request(request) + { + m_uri.AppendFormatV(urlFormat, args); + if (m_request) + m_request->szUrl = m_uri.GetBuffer(); + } + + void Add(const char *fmt, ...) + { + va_list args; + va_start(args, fmt); + m_uri += (m_uri.Find('?') == -1) ? '?' : '&'; + m_uri.AppendFormatV(fmt, args); + va_end(args); + + if (m_request) + m_request->szUrl = m_uri.GetBuffer(); + } + + ~HttpUri() + { + if (m_request) + m_request->szUrl = NULL; + } + +public: + HttpUri& operator=(const HttpUri&); // to prevent copying; + + operator const char*() + { + return m_request + ? m_request->szUrl + : NULL; + } + + HttpUri &operator<<(const VALUE ¶m) + { + Add(param.szName); + return *this; + } + + HttpUri &operator<<(const INT_VALUE ¶m) + { + Add("%s=%i", param.szName, param.iValue); + return *this; + } + + HttpUri &operator<<(const LONG_VALUE ¶m) + { + Add("%s=%lld", param.szName, param.llValue); + return *this; + } + + HttpUri &operator<<(const CHAR_VALUE ¶m) + { + Add("%s=%s", param.szName, param.szValue); + return *this; + } + + HttpUri &operator<<(const ENCODED_VALUE ¶m) + { + Add("%s=%s", param.szName, param.szValue); + return *this; + } +}; + +class HttpHeaders +{ + friend class HttpRequest; + friend class HttpResponse; + +private: + NETLIBHTTPREQUEST *m_request; + + HttpHeaders(NETLIBHTTPREQUEST *request) + : m_request(request) + { + } + + void Add(LPCSTR szName) + { + Add(szName, ""); + } + + void Add(LPCSTR szName, LPCSTR szValue) + { + if (!m_request) + return; + + m_request->headers = (NETLIBHTTPHEADER*)mir_realloc(m_request->headers, + sizeof(NETLIBHTTPHEADER)*(m_request->headersCount + 1)); + m_request->headers[m_request->headersCount].szName = mir_strdup(szName); + m_request->headers[m_request->headersCount].szValue = mir_strdup(szValue); + m_request->headersCount++; + } + +public: + HttpHeaders& operator=(const HttpHeaders&); // to prevent copying; + + const NETLIBHTTPHEADER* operator[](size_t idx) + { + return m_request + ? &m_request->headers[idx] + : NULL; + } + + HttpHeaders& operator<<(const VALUE ¶m) + { + Add(param.szName); + return *this; + } + + HttpHeaders& operator<<(const CHAR_VALUE ¶m) + { + Add(param.szName, param.szValue); + return *this; + } + + HttpHeaders& operator<<(const ENCODED_VALUE ¶m) + { + Add(param.szName, param.szValue); + return *this; + } +}; + +class HttpContent +{ + friend class HttpRequest; + friend class HttpResponse; + +protected: + CMStringA m_content; + NETLIBHTTPREQUEST *m_request; + + HttpContent(NETLIBHTTPREQUEST *request) + : m_request(request) + { + } + + ~HttpContent() + { + if (m_request) + { + m_request->pData = NULL; + m_request->dataLength = 0; + } + } + + void Add(const char *fmt, ...) + { + va_list args; + va_start(args, fmt); + if (!m_content.IsEmpty()) + m_content += '&'; + m_content.AppendFormatV(fmt, args); + va_end(args); + + if (m_request) + { + m_request->pData = m_content.GetBuffer(); + m_request->dataLength = m_content.GetLength(); + } + } + +public: + HttpContent& operator=(const HttpContent&); // to prevent copying; + + bool operator!() const + { + return !m_request || !m_request->pData || !m_request->dataLength; + } + + operator const char*() + { + return m_request + ? m_request->pData + : NULL; + } + + virtual size_t GetSize() const + { + return m_request + ? m_request->dataLength + : 0; + } + + HttpContent & operator<<(const VALUE ¶m) + { + Add(param.szName); + return *this; + } + + HttpContent & operator<<(const INT_VALUE ¶m) + { + Add("%s=%i", param.szName, param.iValue); + return *this; + } + + HttpContent & operator<<(const LONG_VALUE ¶m) + { + Add("%s=%lld", param.szName, param.llValue); + return *this; + } + + HttpContent & operator<<(const CHAR_VALUE ¶m) + { + Add("%s=%s", param.szName, param.szValue); + return *this; + } + + HttpContent &operator<<(const ENCODED_VALUE ¶m) + { + Add("%s=%s", param.szName, param.szValue); + return *this; + } +}; + +enum HttpMethod +{ + HttpGet = 1, + HttpPost +}; + +class HttpResponse +{ + friend class HttpRequest; + +private: + NETLIBHTTPREQUEST *m_response; + +public: + HttpRequest *Request; + HttpHeaders Headers; + HttpContent Content; + + HttpResponse(HttpRequest *request, NETLIBHTTPREQUEST *response) + : Request(request), m_response(response), + Headers(response), Content(response) + { + } + + ~HttpResponse() + { + Netlib_FreeHttpRequest(m_response); + } + + bool IsSuccess() const + { + return m_response->resultCode >= HTTP_CODE_OK && + m_response->resultCode <= HTTP_CODE_MULTI_STATUS; + } + + int GetStatusCode() const + { + return m_response->resultCode; + } +}; + +class HttpRequest : protected NETLIBHTTPREQUEST, public MZeroedObject +{ + friend class HttpUri; + friend class HttpHeaders; + friend class HttpContent; + friend class FormContent; + +public: + HttpUri Uri; + HttpHeaders Headers; + HttpContent Content; + + HttpRequest(HttpMethod method, const char *url) + : Uri(this, url), Headers(this), Content(this) + { + cbSize = sizeof(NETLIBHTTPREQUEST); + requestType = method; + } + + ~HttpRequest() + { + } + + operator NETLIBHTTPREQUEST*() + { + return this; + } +}; + +#endif //_HTTP_REQUEST_H_
\ No newline at end of file diff --git a/protocols/Slack/src/main.cpp b/protocols/Slack/src/main.cpp new file mode 100644 index 0000000000..f74403708b --- /dev/null +++ b/protocols/Slack/src/main.cpp @@ -0,0 +1,54 @@ +#include "stdafx.h" + +int hLangpack; +HINSTANCE g_hInstance; + +PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __AUTHOREMAIL, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {5487475F-00C2-4D88-AE41-C5969260D455} + { 0x5487475f, 0xc2, 0x4d88, {0xae, 0x41, 0xc5, 0x96, 0x92, 0x60, 0xd4, 0x55 }} +}; + +DWORD WINAPI DllMain(HINSTANCE hInstance, DWORD, LPVOID) +{ + g_hInstance = hInstance; + + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD) +{ + return &pluginInfo; +} + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; + +extern "C" int __declspec(dllexport) Load(void) +{ + mir_getLP(&pluginInfo); + + PROTOCOLDESCRIPTOR pd = { sizeof(pd) }; + pd.szName = "SLACK"; + pd.type = PROTOTYPE_PROTOCOL; + pd.fnInit = (pfnInitProto)CSlackProto::InitAccount; + pd.fnUninit = (pfnUninitProto)CSlackProto::UninitAccount; + Proto_RegisterModule(&pd); + + HookEvent(ME_SYSTEM_MODULESLOADED, &CSlackProto::OnModulesLoaded); + + return 0; +} + +extern "C" int __declspec(dllexport) Unload(void) +{ + return 0; +} diff --git a/protocols/Slack/src/resource.h b/protocols/Slack/src/resource.h new file mode 100644 index 0000000000..ecd6bd1008 --- /dev/null +++ b/protocols/Slack/src/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by E:\Projects\C++\miranda-ng\protocols\Slack\res\resource.rc +// +#define IDI_SLACK 100 +#define IDD_OAUTH 120 +#define IDC_OAUTH_CODE 1082 +#define IDC_OAUTH_AUTHORIZE 1200 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 121 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1028 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Slack/src/slack_accounts.cpp b/protocols/Slack/src/slack_accounts.cpp new file mode 100644 index 0000000000..1887c9fc95 --- /dev/null +++ b/protocols/Slack/src/slack_accounts.cpp @@ -0,0 +1,43 @@ +#include "stdafx.h" + +LIST<CSlackProto> CSlackProto::Accounts(1, CSlackProto::CompareAccounts); + +int CSlackProto::CompareAccounts(const CSlackProto *p1, const CSlackProto *p2) +{ + return mir_wstrcmp(p1->m_tszUserName, p2->m_tszUserName); +} + +CSlackProto* CSlackProto::InitAccount(const char *protoName, const wchar_t *userName) +{ + CSlackProto *proto = new CSlackProto(protoName, userName); + Accounts.insert(proto); + return proto; +} + +int CSlackProto::UninitAccount(CSlackProto *proto) +{ + Accounts.remove(proto); + delete proto; + return 0; +} + +CSlackProto* CSlackProto::GetContactAccount(MCONTACT hContact) +{ + for (int i = 0; i < Accounts.getCount(); i++) + if (mir_strcmpi(GetContactProto(hContact), Accounts[i]->m_szModuleName) == 0) + return Accounts[i]; + return NULL; +} + +int CSlackProto::OnAccountLoaded(WPARAM, LPARAM) +{ + HookProtoEvent(ME_OPT_INITIALISE, &CSlackProto::OnOptionsInit); + HookProtoEvent(ME_MSG_PRECREATEEVENT, &CSlackProto::OnPreCreateMessage); + + return 0; +} + +INT_PTR CSlackProto::OnAccountManagerInit(WPARAM, LPARAM lParam) +{ + return (INT_PTR)(CSlackOptionsMain::CreateAccountManagerPage(this, (HWND)lParam))->GetHwnd(); +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_connection.cpp b/protocols/Slack/src/slack_connection.cpp new file mode 100644 index 0000000000..b8b0652618 --- /dev/null +++ b/protocols/Slack/src/slack_connection.cpp @@ -0,0 +1,75 @@ +#include "stdafx.h" + +void CSlackProto::Login() +{ + ptrA token(getStringA("TokenSecret")); + if (mir_strlen(token)) + { + PushRequest(new GetUserListRequest(token), &CSlackProto::OnGotUserList); + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_CONNECTING, m_iStatus = m_iDesiredStatus); + return; + } + + CSlackOAuth oauth(this); + if (!oauth.DoModal()) + { + SetStatus(ID_STATUS_OFFLINE); + return; + } + + HttpRequest *request = new HttpRequest(HttpMethod::HttpPost, SLACK_API_URL "/oauth.access"); + request->Headers + << CHAR_VALUE("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); + request->Content + << CHAR_VALUE("client_id", SLACK_CLIENT_ID) + << CHAR_VALUE("client_secret", SLACK_CLIENT_SECRET) + << ENCODED_VALUE("code", oauth.GetAuthCode()) + << ENCODED_VALUE("redirect_uri", SLACK_REDIRECT_URL); + + PushRequest(request, &CSlackProto::OnAuthorize); +} + +void CSlackProto::OnAuthorize(JSONNode &root) +{ + if (!root) + { + SetStatus(ID_STATUS_OFFLINE); + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, NULL); + return; + } + + bool isOk = root["ok"].as_bool(); + if (!isOk) + { + SetStatus(ID_STATUS_OFFLINE); + json_string error = root["error"].as_string(); + debugLogA(__FUNCTION__": %s", error); + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID); + return; + } + + json_string token = root["access_token"].as_string(); + setString("TokenSecret", token.c_str()); + + json_string userId = root["user_id"].as_string(); + setString("UserId", userId.c_str()); + + CMStringW teamName = root["team_name"].as_mstring(); + setWString("TeamName", teamName); + if (!teamName.IsEmpty() > 0 && !Clist_GroupExists(teamName)) + Clist_GroupCreate(0, teamName); + + json_string teamId = root["team_id"].as_string(); + setString("TeamId", userId.c_str()); + + PushRequest(new GetUserListRequest(token.c_str()), &CSlackProto::OnGotUserList); + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_CONNECTING, m_iStatus = m_iDesiredStatus); +} + +void CSlackProto::LogOut() +{ + isTerminated = true; + if (hRequestQueueThread) + SetEvent(hRequestsQueueEvent); +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_contacts.cpp b/protocols/Slack/src/slack_contacts.cpp new file mode 100644 index 0000000000..185d5110b8 --- /dev/null +++ b/protocols/Slack/src/slack_contacts.cpp @@ -0,0 +1,165 @@ +#include "stdafx.h" + +WORD CSlackProto::GetContactStatus(MCONTACT hContact) +{ + return getWord(hContact, "Status", ID_STATUS_OFFLINE); +} + +void CSlackProto::SetContactStatus(MCONTACT hContact, WORD status) +{ + WORD oldStatus = GetContactStatus(hContact); + if (oldStatus != status) + setWord(hContact, "Status", status); +} + +void CSlackProto::SetAllContactsStatus(WORD status) +{ + for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) + SetContactStatus(hContact, status); +} + +MCONTACT CSlackProto::GetContact(const char *userId) +{ + MCONTACT hContact = NULL; + for (hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) + { + ptrA contactPhone(getStringA(hContact, "UserId")); + if (mir_strcmp(userId, contactPhone) == 0) + break; + } + return hContact; +} + +MCONTACT CSlackProto::AddContact(const char *userId, const char *nick, bool isTemporary) +{ + MCONTACT hContact = GetContact(userId); + if (!hContact) + { + hContact = db_add_contact(); + Proto_AddToContact(hContact, m_szModuleName); + + setString(hContact, "UserId", userId); + + if (mir_strlen(nick)) + setWString(hContact, "Nick", ptrW(mir_utf8decodeW(nick))); + + DBVARIANT dbv; + if (!getWString("TeamName", &dbv) && Clist_GroupExists(dbv.ptszVal)) + { + db_set_ws(hContact, "CList", "Group", dbv.ptszVal); + db_free(&dbv); + } + + setByte(hContact, "Auth", 1); + setByte(hContact, "Grant", 1); + + if (isTemporary) + { + db_set_b(hContact, "CList", "NotOnList", 1); + } + } + return hContact; +} + +void CSlackProto::OnGotUserProfile(JSONNode &root) +{ + if (!root) + { + debugLogA(__FUNCTION__": failed to load user profile"); + return; + } + + bool isOk = root["ok"].as_bool(); + if (!isOk) + { + debugLogA(__FUNCTION__": failed to load users profile"); + return; + } + + JSONNode profile = root["profile"].as_node(); +} + +void CSlackProto::OnGotUserProfile(MCONTACT hContact, JSONNode &root) +{ + if (!root) + { + debugLogA(__FUNCTION__": failed to read users profile"); + return; + } + + CMStringW firstName = root["first_name"].as_mstring(); + setWString(hContact, "FirstName", firstName); + + CMStringW lastName = root["last_name"].as_mstring(); + setWString(hContact, "LastName", lastName); +} + +void CSlackProto::OnGotUserList(JSONNode &root) +{ + if (!root) + { + debugLogA(__FUNCTION__": failed to load users list"); + return; + } + + bool isOk = root["ok"].as_bool(); + if (!isOk) + { + debugLogA(__FUNCTION__": failed to load users list"); + return; + } + + JSONNode users = root["members"].as_array(); + for (size_t i = 0; i < users.size(); i++) + { + JSONNode user = users[i]; + + json_string userId = user["id"].as_string(); + json_string nick = user["name"].as_string(); + bool isDeleted = user["deleted"].as_bool(); + MCONTACT hContact = AddContact(userId.c_str(), nick.c_str(), isDeleted); + if (hContact) + { + json_string teamId = user["team_id"].as_string(); + setString(hContact, "TeamId", teamId.c_str()); + + CMStringW status = root["status"].as_mstring(); + if (!status.IsEmpty()) + setWString(hContact, "StatusMsg", status); + + JSONNode profile = root["profile"].as_node(); + OnGotUserProfile(hContact, profile); + } + } +} + +INT_PTR CSlackProto::OnRequestAuth(WPARAM hContact, LPARAM lParam) +{ + if (!IsOnline()) + { + return -1; // ??? + } + return 0; +} + +INT_PTR CSlackProto::OnGrantAuth(WPARAM hContact, LPARAM) +{ + if (!IsOnline()) + { + // TODO: warn + return 0; + } + + return 0; +} + +int CSlackProto::OnContactDeleted(MCONTACT hContact, LPARAM) +{ + if (!IsOnline()) + { + // TODO: warn + return 0; + } + + return 0; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_dialogs.cpp b/protocols/Slack/src/slack_dialogs.cpp new file mode 100644 index 0000000000..b495af842e --- /dev/null +++ b/protocols/Slack/src/slack_dialogs.cpp @@ -0,0 +1,36 @@ +#include "stdafx.h" + +CSlackOAuth::CSlackOAuth(CSlackProto *proto) + : CSuper(proto, IDD_OAUTH, false), + m_authorize(this, IDC_OAUTH_AUTHORIZE, SLACK_URL "/oauth/authorize?scope=identify+read+post&redirect_uri=" SLACK_REDIRECT_URL "&client_id=" SLACK_CLIENT_ID), + m_code(this, IDC_OAUTH_CODE), m_ok(this, IDOK) +{ + m_ok.OnClick = Callback(this, &CSlackOAuth::OnOk); + + Utils_RestoreWindowPosition(m_hwnd, NULL, m_proto->m_szModuleName, "OAuthWindow"); +} + +void CSlackOAuth::OnInitDialog() +{ + CSuper::OnInitDialog(); + + Window_SetIcon_IcoLib(m_hwnd, m_proto->GetIconHandle(IDI_SLACK)); + + SendMessage(m_code.GetHwnd(), EM_LIMITTEXT, 40, 0); +} + +void CSlackOAuth::OnOk(CCtrlButton*) +{ + mir_strncpy(m_authCode, ptrA(m_code.GetTextA()), _countof(m_authCode)); + EndDialog(m_hwnd, 1); +} + +void CSlackOAuth::OnClose() +{ + Utils_SaveWindowPosition(m_hwnd, NULL, m_proto->m_szModuleName, "OAuthWindow"); +} + +const char* CSlackOAuth::GetAuthCode() +{ + return m_authCode; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_dialogs.h b/protocols/Slack/src/slack_dialogs.h new file mode 100644 index 0000000000..cd7f78226d --- /dev/null +++ b/protocols/Slack/src/slack_dialogs.h @@ -0,0 +1,26 @@ +#ifndef _SLACK_DIALOGS_H_ +#define _SLACK_DIALOGS_H_ + +class CSlackOAuth : public CProtoDlgBase<CSlackProto> +{ + typedef CProtoDlgBase<CSlackProto> CSuper; + +private: + char m_authCode[40]; + + CCtrlHyperlink m_authorize; + CCtrlEdit m_code; + CCtrlButton m_ok; + +protected: + void OnInitDialog(); + void OnOk(CCtrlButton*); + void OnClose(); + +public: + CSlackOAuth(CSlackProto *proto); + + const char *GetAuthCode(); +}; + +#endif //_SLACK_DIALOGS_H_ diff --git a/protocols/Slack/src/slack_events.cpp b/protocols/Slack/src/slack_events.cpp new file mode 100644 index 0000000000..233b662594 --- /dev/null +++ b/protocols/Slack/src/slack_events.cpp @@ -0,0 +1,9 @@ +#include "stdafx.h" + +int CSlackProto::OnModulesLoaded(WPARAM, LPARAM) +{ + CSlackProto::InitIcons(); + //CSlackProto::InitMenus(); + + return 0; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_icons.cpp b/protocols/Slack/src/slack_icons.cpp new file mode 100644 index 0000000000..2bb652e444 --- /dev/null +++ b/protocols/Slack/src/slack_icons.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" + +IconItemT CSlackProto::Icons[] = +{ + { LPGENW("Protocol icon"), "main", IDI_SLACK }, +}; + +void CSlackProto::InitIcons() +{ + Icon_RegisterT(g_hInstance, LPGENW("Protocols") L"/" _A2W(MODULE), Icons, _countof(Icons), MODULE); +} + +HANDLE CSlackProto::GetIconHandle(int iconId) +{ + for (size_t i = 0; i < _countof(Icons); i++) + if (Icons[i].defIconID == iconId) + return Icons[i].hIcolib; + + return NULL; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_menus.cpp b/protocols/Slack/src/slack_menus.cpp new file mode 100644 index 0000000000..183971237f --- /dev/null +++ b/protocols/Slack/src/slack_menus.cpp @@ -0,0 +1,57 @@ +#include "stdafx.h" + +HGENMENU CSlackProto::ContactMenuItems[CMI_MAX]; + +int CSlackProto::OnPrebuildContactMenu(WPARAM hContact, LPARAM) +{ + if (!hContact) + return 0; + + if (!this->IsOnline()) + return 0; + + if (this->isChatRoom(hContact)) + return 0; + + bool isCtrlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + + bool isAuthNeed = getByte(hContact, "Auth", 0) > 0; + Menu_ShowItem(ContactMenuItems[CMI_AUTH_REQUEST], isCtrlPressed || isAuthNeed); + + bool isGrantNeed = getByte(hContact, "Grant", 0) > 0; + Menu_ShowItem(ContactMenuItems[CMI_AUTH_GRANT], isCtrlPressed || isGrantNeed); + + return 0; +} + +int CSlackProto::PrebuildContactMenu(WPARAM hContact, LPARAM lParam) +{ + for (int i = 0; i < _countof(ContactMenuItems); i++) + Menu_ShowItem(ContactMenuItems[i], FALSE); + CSlackProto *proto = CSlackProto::GetContactAccount(hContact); + return proto ? proto->OnPrebuildContactMenu(hContact, lParam) : 0; +} + +void CSlackProto::InitMenus() +{ + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, &CSlackProto::PrebuildContactMenu); + + CMenuItem mi; + mi.flags = CMIF_UNICODE; + /* + // Request authorization + mi.pszService = MODULE"/RequestAuth"; + mi.ptszName = LPGENT("Request authorization"); + mi.position = CMI_POSITION + CMI_AUTH_REQUEST; + mi.icolibItem = LoadSkinnedIconHandle(SKINICON_AUTH_REQUEST); + ContactMenuItems[CMI_AUTH_REQUEST] = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, GlobalService<&CSlackProto::OnRequestAuth>); + + // Grant authorization + mi.pszService = MODULE"/GrantAuth"; + mi.ptszName = LPGENT("Grant authorization"); + mi.position = CMI_POSITION + CMI_AUTH_GRANT; + mi.icolibItem = LoadSkinnedIconHandle(SKINICON_AUTH_GRANT); + ContactMenuItems[CMI_AUTH_GRANT] = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, GlobalService<&CSlackProto::OnGrantAuth>);*/ +} diff --git a/protocols/Slack/src/slack_menus.h b/protocols/Slack/src/slack_menus.h new file mode 100644 index 0000000000..7270a6d6db --- /dev/null +++ b/protocols/Slack/src/slack_menus.h @@ -0,0 +1,20 @@ +#ifndef _SLACK_MENUS_H_ +#define _SLACK_MENUS_H_ + +#define CMI_POSITION -201001000 + +enum CMI_MENU_ITEMS +{ + CMI_AUTH_REQUEST, + CMI_AUTH_GRANT, + CMI_MAX // this item shall be the last one +}; + +#define SMI_POSITION 200000 + +enum SMI_MENU_ITEMS +{ + SMI_MAX // this item shall be the last one +}; + +#endif //_SLACK_MENUS_H_ diff --git a/protocols/Slack/src/slack_messages.cpp b/protocols/Slack/src/slack_messages.cpp new file mode 100644 index 0000000000..f78ba3e9db --- /dev/null +++ b/protocols/Slack/src/slack_messages.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" + +/* MESSAGE RECEIVING */ + +// incoming message flow + + +// writing message/even into db +int CSlackProto::OnReceiveMessage(MCONTACT hContact, PROTORECVEVENT *pre) +{ + //return Proto_RecvMessage(hContact, pre); + if (pre->szMessage == NULL) + return NULL; + + DBEVENTINFO dbei = {}; + dbei.szModule = GetContactProto(hContact); + dbei.timestamp = pre->timestamp; + dbei.flags = 0; + dbei.eventType = pre->lParam; + dbei.cbBlob = (DWORD)mir_strlen(pre->szMessage) + 1; + dbei.pBlob = (PBYTE)pre->szMessage; + + return (INT_PTR)db_event_add(hContact, &dbei); +} + +/* MESSAGE SENDING */ + +// outcoming message flow +int CSlackProto::OnSendMessage(MCONTACT hContact, int flags, const char *szMessage) +{ + return 0; +} + +// message is received by the other side + +// preparing message/action to writing into db +int CSlackProto::OnPreCreateMessage(WPARAM, LPARAM lParam) +{ + MessageWindowEvent *evt = (MessageWindowEvent*)lParam; + if (mir_strcmp(GetContactProto(evt->hContact), m_szModuleName)) + return 0; +} + +/* TYPING */ + +int CSlackProto::OnUserIsTyping(MCONTACT hContact, int type) +{ + return 0; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_netlib.cpp b/protocols/Slack/src/slack_netlib.cpp new file mode 100644 index 0000000000..85b832e152 --- /dev/null +++ b/protocols/Slack/src/slack_netlib.cpp @@ -0,0 +1,21 @@ +#include "stdafx.h" + +void CSlackProto::InitNetlib() +{ + wchar_t name[128]; + mir_snwprintf(name, TranslateT("%s connection"), m_tszUserName); + + NETLIBUSER nlu = {}; + nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szDescriptiveName.w = name; + nlu.szSettingsModule = m_szModuleName; + m_hNetlibUser = Netlib_RegisterUser(&nlu); + + debugLogA(__FUNCTION__":Setting protocol / module name to '%s'", m_szModuleName); +} + +void CSlackProto::UninitNetlib() +{ + Netlib_CloseHandle(hNetlib); + hNetlib = NULL; +} diff --git a/protocols/Slack/src/slack_options.cpp b/protocols/Slack/src/slack_options.cpp new file mode 100644 index 0000000000..49544935c1 --- /dev/null +++ b/protocols/Slack/src/slack_options.cpp @@ -0,0 +1,43 @@ +#include "stdafx.h" + +CSlackOptionsMain::CSlackOptionsMain(CSlackProto *proto, int idDialog) + : CSuper(proto, idDialog, false), + m_team(this, IDC_TEAM), m_email(this, IDC_EMAIL), + m_password(this, IDC_PASSWORD), m_group(this, IDC_GROUP) +{ + + CreateLink(m_team, "Team", L""); + CreateLink(m_email, "Email", L""); + CreateLink(m_password, "Password", L""); + CreateLink(m_group, "DefaultGroup", _A2W(MODULE)); +} + +void CSlackOptionsMain::OnInitDialog() +{ + CSuper::OnInitDialog(); + + SendMessage(m_team.GetHwnd(), EM_LIMITTEXT, 21, 0); + SendMessage(m_email.GetHwnd(), EM_LIMITTEXT, 40, 0); + SendMessage(m_password.GetHwnd(), EM_LIMITTEXT, 40, 0); + SendMessage(m_group.GetHwnd(), EM_LIMITTEXT, 64, 0); +} + +void CSlackOptionsMain::OnApply() +{ +} + +///////////////////////////////////////////////////////////////////////////////// + +int CSlackProto::OnOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.szTitle.w = m_tszUserName; + odp.flags = ODPF_BOLDGROUPS | ODPF_UNICODE | ODPF_DONTTRANSLATE; + odp.szGroup.w = LPGENW("Network"); + + odp.szTab.w = LPGENW("Account"); + odp.pDialog = CSlackOptionsMain::CreateOptionsPage(this); + //Options_AddPage(wParam, &odp); + + return 0; +} diff --git a/protocols/Slack/src/slack_options.h b/protocols/Slack/src/slack_options.h new file mode 100644 index 0000000000..e0eeda70a3 --- /dev/null +++ b/protocols/Slack/src/slack_options.h @@ -0,0 +1,32 @@ +#ifndef _SLACK_OPTIONS_H_ +#define _SLACK_OPTIONS_H_ + +class CSlackOptionsMain : public CProtoDlgBase<CSlackProto> +{ + typedef CProtoDlgBase<CSlackProto> CSuper; + +private: + CCtrlEdit m_team; + CCtrlEdit m_email; + CCtrlEdit m_password; + CCtrlEdit m_group; + +protected: + void OnInitDialog(); + void OnApply(); + +public: + CSlackOptionsMain(CSlackProto *proto, int idDialog); + + static CDlgBase *CreateAccountManagerPage(void *param, HWND owner) + { + CSlackOptionsMain *page = new CSlackOptionsMain((CSlackProto*)param, IDD_ACCOUNT_MANAGER); + page->SetParent(owner); + page->Show(); + return page; + } + + static CDlgBase *CreateOptionsPage(void *param) { return new CSlackOptionsMain((CSlackProto*)param, IDD_OPTIONS_MAIN); } +}; + +#endif //_SLACK_OPTIONS_H_ diff --git a/protocols/Slack/src/slack_proto.cpp b/protocols/Slack/src/slack_proto.cpp new file mode 100644 index 0000000000..8a37838c28 --- /dev/null +++ b/protocols/Slack/src/slack_proto.cpp @@ -0,0 +1,147 @@ +#include "stdafx.h" + +CSlackProto::CSlackProto(const char* protoName, const TCHAR* userName) : + PROTO<CSlackProto>(protoName, userName), requestQueue(1) +{ + InitNetlib(); + + //CreateProtoService(PS_CREATEACCMGRUI, &CSlackProto::OnAccountManagerInit); + + SetAllContactsStatus(ID_STATUS_OFFLINE); +} + +CSlackProto::~CSlackProto() +{ + UninitNetlib(); +} + +DWORD_PTR CSlackProto::GetCaps(int type, MCONTACT) +{ + switch (type) + { + case PFLAGNUM_1: + return PF1_IM; + case PFLAGNUM_2: + return PF2_ONLINE | PF2_LONGAWAY; + case PFLAGNUM_3: + return PF2_ONLINE | PF2_LONGAWAY; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)"User Id"; + case PFLAG_UNIQUEIDSETTING: + return (DWORD_PTR)"UserId"; + } + + return 0; +} + +MCONTACT CSlackProto::AddToList(int flags, PROTOSEARCHRESULT *psr) +{ + return NULL; +} + +int CSlackProto::AuthRecv(MCONTACT, PROTORECVEVENT* pre) +{ + return Proto_AuthRecv(m_szModuleName, pre); +} + +int CSlackProto::AuthRequest(MCONTACT hContact, const wchar_t *szMessage) +{ + ptrA reason(mir_utf8encodeW(szMessage)); + return OnRequestAuth(hContact, (LPARAM)reason); +} + +int CSlackProto::RecvMsg(MCONTACT hContact, PROTORECVEVENT *pre) +{ + return OnReceiveMessage(hContact, pre); +} + +int CSlackProto::SendMsg(MCONTACT hContact, int flags, const char *msg) +{ + return OnSendMessage(hContact, flags, msg); +} + +int CSlackProto::SetStatus(int iNewStatus) +{ + if (iNewStatus == m_iDesiredStatus) + return 0; + + switch (iNewStatus) + { + case ID_STATUS_FREECHAT: + case ID_STATUS_ONTHEPHONE: + iNewStatus = ID_STATUS_ONLINE; + break; + + case ID_STATUS_NA: + case ID_STATUS_OUTTOLUNCH: + iNewStatus = ID_STATUS_AWAY; + break; + + case ID_STATUS_DND: + case ID_STATUS_INVISIBLE: + iNewStatus = ID_STATUS_OCCUPIED; + break; + } + + debugLogA(__FUNCTION__ ": changing status from %i to %i", m_iStatus, iNewStatus); + + int old_status = m_iStatus; + m_iDesiredStatus = iNewStatus; + + if (iNewStatus == ID_STATUS_OFFLINE) + { + // logout + LogOut(); + + if (!Miranda_IsTerminated()) + { + SetAllContactsStatus(ID_STATUS_OFFLINE); + } + + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + } + else + { + if (old_status == ID_STATUS_CONNECTING) + { + return 0; + } + + if (old_status == ID_STATUS_OFFLINE && !IsOnline()) + { + // login + m_iStatus = ID_STATUS_CONNECTING; + + isTerminated = false; + + hRequestQueueThread = ForkThreadEx(&CSlackProto::RequestQueueThread, NULL, NULL); + } + else + { + // set tox status + m_iStatus = iNewStatus; + } + } + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); + return 0; +} + +int CSlackProto::UserIsTyping(MCONTACT hContact, int type) +{ + return OnUserIsTyping(hContact, type); +} + +int CSlackProto::OnEvent(PROTOEVENTTYPE iEventType, WPARAM wParam, LPARAM lParam) +{ + switch (iEventType) + { + case EV_PROTO_ONLOAD: + return OnAccountLoaded(wParam, lParam); + + case EV_PROTO_ONCONTACTDELETED: + return OnContactDeleted(wParam, lParam); + } + + return 1; +} diff --git a/protocols/Slack/src/slack_proto.h b/protocols/Slack/src/slack_proto.h new file mode 100644 index 0000000000..f47c70f946 --- /dev/null +++ b/protocols/Slack/src/slack_proto.h @@ -0,0 +1,146 @@ +#ifndef _SLACK_PROTO_H_ +#define _SLACK_PROTO_H_ + +struct CSlackProto : public PROTO<CSlackProto> +{ + friend class CSlackOptionsMain; + friend class CSlackOAuth; + + typedef void(CSlackProto::*HttpCallback)(HttpResponse&); + typedef void(CSlackProto::*JsonCallback)(JSONNode&); + struct RequestQueueItem + { + HttpRequest *request; + HttpCallback httpCallback; + JsonCallback jsonCallback; + }; + +public: + ////////////////////////////////////////////////////////////////////////////////////// + //Ctors + + CSlackProto(const char *protoName, const wchar_t *userName); + ~CSlackProto(); + + ////////////////////////////////////////////////////////////////////////////////////// + // Virtual functions + + virtual MCONTACT __cdecl AddToList(int flags, PROTOSEARCHRESULT* psr); + + virtual int __cdecl AuthRecv(MCONTACT hContact, PROTORECVEVENT*); + virtual int __cdecl AuthRequest(MCONTACT hContact, const wchar_t* szMessage); + + virtual DWORD_PTR __cdecl GetCaps(int type, MCONTACT hContact = NULL); + + virtual int __cdecl RecvMsg(MCONTACT hContact, PROTORECVEVENT*); + virtual int __cdecl SendMsg(MCONTACT hContact, int flags, const char* msg); + + virtual int __cdecl SetStatus(int iNewStatus); + + virtual int __cdecl UserIsTyping(MCONTACT hContact, int type); + + virtual int __cdecl OnEvent(PROTOEVENTTYPE iEventType, WPARAM wParam, LPARAM lParam); + + // accounts + static CSlackProto* InitAccount(const char *protoName, const TCHAR *userName); + static int UninitAccount(CSlackProto *proto); + + // icons + static void InitIcons(); + + // menus + static void InitMenus(); + + static int OnModulesLoaded(WPARAM, LPARAM); + +private: + HANDLE hNetlib; + bool isTerminated, isConnected; + mir_cs requestQueueLock; + HANDLE hRequestsQueueEvent; + HANDLE hRequestQueueThread; + LIST<RequestQueueItem> requestQueue; + + // requests + void SendRequest(HttpRequest *request); + void SendRequest(HttpRequest *request, HttpCallback callback); + void SendRequest(HttpRequest *request, JsonCallback callback); + void PushRequest(HttpRequest *request); + void PushRequest(HttpRequest *request, HttpCallback callback); + void PushRequest(HttpRequest *request, JsonCallback callback); + void __cdecl RequestQueueThread(void*); + + // network + bool IsOnline(); + + // accounts + static LIST<CSlackProto> Accounts; + static int CompareAccounts(const CSlackProto *p1, const CSlackProto *p2); + + static CSlackProto* GetContactAccount(MCONTACT hContact); + + int __cdecl OnAccountLoaded(WPARAM, LPARAM); + + INT_PTR __cdecl OnAccountManagerInit(WPARAM, LPARAM); + + // netlib + void InitNetlib(); + void UninitNetlib(); + + // login + void Login(); + void LogOut(); + void OnAuthorize(JSONNode &root); + + // icons + static IconItemT Icons[]; + static HANDLE GetIconHandle(int iconId); + + // menus + static HGENMENU ContactMenuItems[CMI_MAX]; + int OnPrebuildContactMenu(WPARAM hContact, LPARAM); + static int PrebuildContactMenu(WPARAM hContact, LPARAM lParam); + + // options + int __cdecl OnOptionsInit(WPARAM wParam, LPARAM lParam); + + // contacts + WORD GetContactStatus(MCONTACT hContact); + void SetContactStatus(MCONTACT hContact, WORD status); + void SetAllContactsStatus(WORD status); + + MCONTACT GetContact(const char *userId); + MCONTACT AddContact(const char *userId, const char *nick, bool isTemporary = false); + + void OnGotUserProfile(JSONNode &root); + void OnGotUserProfile(MCONTACT hContact, JSONNode &root); + void OnGotUserList(JSONNode &root); + + INT_PTR __cdecl OnRequestAuth(WPARAM hContact, LPARAM lParam); + INT_PTR __cdecl OnGrantAuth(WPARAM hContact, LPARAM); + + int __cdecl OnContactDeleted(MCONTACT, LPARAM); + + // messages + int OnReceiveMessage(MCONTACT hContact, PROTORECVEVENT *pre); + int OnSendMessage(MCONTACT hContact, int flags, const char *message); + + int OnUserIsTyping(MCONTACT hContact, int type); + + int __cdecl OnPreCreateMessage(WPARAM wParam, LPARAM lParam); + + // utils + static void ShowNotification(const TCHAR *message, int flags = 0, MCONTACT hContact = NULL); + static void ShowNotification(const TCHAR *caption, const TCHAR *message, int flags = 0, MCONTACT hContact = NULL); + + MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD timestamp, DWORD flags, PBYTE pBlob, DWORD cbBlob); + + template<INT_PTR(__cdecl CSlackProto::*Service)(WPARAM, LPARAM)> + static INT_PTR __cdecl GlobalService(WPARAM wParam, LPARAM lParam) + { + CSlackProto *proto = CSlackProto::GetContactAccount((MCONTACT)wParam); + return proto ? (proto->*Service)(wParam, lParam) : 0; + } +}; + +#endif //_SLACK_PROTO_H_ diff --git a/protocols/Slack/src/slack_requests.cpp b/protocols/Slack/src/slack_requests.cpp new file mode 100644 index 0000000000..7fd0ef4805 --- /dev/null +++ b/protocols/Slack/src/slack_requests.cpp @@ -0,0 +1,95 @@ +#include "stdafx.h" + +void CSlackProto::SendRequest(HttpRequest *request) +{ + NETLIBHTTPREQUEST *pResp = Netlib_HttpTransaction(m_hNetlibUser, (NETLIBHTTPREQUEST*)request); + HttpResponse response(request, pResp); + delete request; +} + +void CSlackProto::SendRequest(HttpRequest *request, HttpCallback callback) +{ + NETLIBHTTPREQUEST *pResp = Netlib_HttpTransaction(m_hNetlibUser, (NETLIBHTTPREQUEST*)request); + HttpResponse response(request, pResp); + if (callback) + (this->*callback)(response); + delete request; +} + +void CSlackProto::SendRequest(HttpRequest *request, JsonCallback callback) +{ + NETLIBHTTPREQUEST *pResp = Netlib_HttpTransaction(m_hNetlibUser, (NETLIBHTTPREQUEST*)request); + HttpResponse response(request, pResp); + if (callback) + { + JSONNode root = JSONNode::parse(response.Content); + (this->*callback)(root); + } + delete request; +} + +void CSlackProto::PushRequest(HttpRequest *request) +{ + RequestQueueItem *item = new RequestQueueItem(); + item->request = request; + { + mir_cslock lock(requestQueueLock); + requestQueue.insert(item); + } + SetEvent(hRequestsQueueEvent); +} + +void CSlackProto::PushRequest(HttpRequest *request, HttpCallback callback) +{ + RequestQueueItem *item = new RequestQueueItem(); + item->request = request; + item->httpCallback = callback; + { + mir_cslock lock(requestQueueLock); + requestQueue.insert(item); + } + SetEvent(hRequestsQueueEvent); +} + +void CSlackProto::PushRequest(HttpRequest *request, JsonCallback callback) +{ + RequestQueueItem *item = new RequestQueueItem(); + item->request = request; + item->jsonCallback = callback; + { + mir_cslock lock(requestQueueLock); + requestQueue.insert(item); + } + SetEvent(hRequestsQueueEvent); +} + +void CSlackProto::RequestQueueThread(void*) +{ + Login(); + + do + { + RequestQueueItem *item; + while (true) + { + { + mir_cslock lock(requestQueueLock); + if (!requestQueue.getCount()) + break; + + item = requestQueue[0]; + requestQueue.remove(0); + } + if (item->httpCallback) + SendRequest(item->request, item->httpCallback); + else if (item->jsonCallback) + SendRequest(item->request, item->jsonCallback); + else + SendRequest(item->request); + delete item; + } + WaitForSingleObject(hRequestsQueueEvent, 1000); + } while (!isTerminated); + + hRequestQueueThread = NULL; +}
\ No newline at end of file diff --git a/protocols/Slack/src/slack_utils.cpp b/protocols/Slack/src/slack_utils.cpp new file mode 100644 index 0000000000..bd40f58ee7 --- /dev/null +++ b/protocols/Slack/src/slack_utils.cpp @@ -0,0 +1,45 @@ +#include "stdafx.h" + +bool CSlackProto::IsOnline() +{ + return false; +} + +void CSlackProto::ShowNotification(const TCHAR *caption, const TCHAR *message, int flags, MCONTACT hContact) +{ + if (Miranda_IsTerminated()) + { + return; + } + + if (ServiceExists(MS_POPUP_ADDPOPUPT) && db_get_b(NULL, "Popup", "ModuleIsEnabled", 1)) + { + POPUPDATAT ppd = { 0 }; + ppd.lchContact = hContact; + wcsncpy(ppd.lpwzContactName, caption, MAX_CONTACTNAME); + wcsncpy(ppd.lpwzText, message, MAX_SECONDLINE); + ppd.lchIcon = IcoLib_GetIcon("Slack_main"); + + if (!PUAddPopupT(&ppd)) + return; + } + + MessageBox(NULL, message, caption, MB_OK | flags); +} + +void CSlackProto::ShowNotification(const TCHAR *message, int flags, MCONTACT hContact) +{ + ShowNotification(_A2W(MODULE), message, flags, hContact); +} + +MEVENT CSlackProto::AddEventToDb(MCONTACT hContact, WORD type, DWORD timestamp, DWORD flags, PBYTE pBlob, DWORD cbBlob) +{ + DBEVENTINFO dbei = {}; + dbei.szModule = this->m_szModuleName; + dbei.timestamp = timestamp; + dbei.eventType = type; + dbei.cbBlob = cbBlob; + dbei.pBlob = pBlob; + dbei.flags = flags; + return db_event_add(hContact, &dbei); +}
\ No newline at end of file diff --git a/protocols/Slack/src/stdafx.cxx b/protocols/Slack/src/stdafx.cxx new file mode 100644 index 0000000000..e6f635a0de --- /dev/null +++ b/protocols/Slack/src/stdafx.cxx @@ -0,0 +1,18 @@ +/* +Copyright (C) 2012-15 Miranda NG project (http://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h"
\ No newline at end of file diff --git a/protocols/Slack/src/stdafx.h b/protocols/Slack/src/stdafx.h new file mode 100644 index 0000000000..ce5ebf1a80 --- /dev/null +++ b/protocols/Slack/src/stdafx.h @@ -0,0 +1,58 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include <windows.h> +#include <commctrl.h> +#include <time.h> + +#include <newpluginapi.h> + +#include <m_protoint.h> +#include <m_protosvc.h> + +#include <m_system.h> +#include <m_database.h> +#include <m_langpack.h> +#include <m_options.h> +#include <m_netlib.h> +#include <m_popup.h> +#include <m_icolib.h> +#include <m_userinfo.h> +#include <m_addcontact.h> +#include <m_message.h> +#include <m_avatars.h> +#include <m_skin.h> +#include <m_chat.h> +#include <m_genmenu.h> +#include <m_clc.h> +#include <m_clist.h> +#include <m_clistint.h> +#include <m_gui.h> +#include <m_http.h> +#include <m_json.h> + +struct CSlackProto; + +#define MODULE "Slack" + +#define SLACK_URL "https://slack.com" +#define SLACK_API_URL SLACK_URL "/api" +#define SLACK_REDIRECT_URL "http://www.miranda-ng.org/slack" + +#define SLACK_CLIENT_ID "149178180673.150539976630" +#include "../../../miranda-private-keys/Slack/client_secret.h" + +#include "http_request.h" + +#include "version.h" +#include "resource.h" +#include "slack_menus.h" +#include "slack_dialogs.h" +#include "slack_options.h" +#include "slack_proto.h" + +#include "api\api_users.h" + +extern HINSTANCE g_hInstance; + +#endif //_COMMON_H_
\ No newline at end of file diff --git a/protocols/Slack/src/version.h b/protocols/Slack/src/version.h new file mode 100644 index 0000000000..e76c9c223d --- /dev/null +++ b/protocols/Slack/src/version.h @@ -0,0 +1,14 @@ +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 11 +#define __RELEASE_NUM 0 +#define __BUILD_NUM 0 + +#include <stdver.h> + +#define __PLUGIN_NAME "Slack" +#define __FILENAME "Slack.dll" +#define __DESCRIPTION "Slack for Miranda NG." +#define __AUTHOR "Miranda NG Team" +#define __AUTHOREMAIL "" +#define __AUTHORWEB "http://miranda-ng.org/p/Slack/" +#define __COPYRIGHT "© 2017 Miranda NG Team" |