summaryrefslogtreecommitdiff
path: root/protocols/Slack/src
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Slack/src')
-rw-r--r--protocols/Slack/src/api/api_users.h18
-rw-r--r--protocols/Slack/src/http_request.h347
-rw-r--r--protocols/Slack/src/main.cpp54
-rw-r--r--protocols/Slack/src/resource.h19
-rw-r--r--protocols/Slack/src/slack_accounts.cpp43
-rw-r--r--protocols/Slack/src/slack_connection.cpp75
-rw-r--r--protocols/Slack/src/slack_contacts.cpp165
-rw-r--r--protocols/Slack/src/slack_dialogs.cpp36
-rw-r--r--protocols/Slack/src/slack_dialogs.h26
-rw-r--r--protocols/Slack/src/slack_events.cpp9
-rw-r--r--protocols/Slack/src/slack_icons.cpp20
-rw-r--r--protocols/Slack/src/slack_menus.cpp57
-rw-r--r--protocols/Slack/src/slack_menus.h20
-rw-r--r--protocols/Slack/src/slack_messages.cpp49
-rw-r--r--protocols/Slack/src/slack_netlib.cpp21
-rw-r--r--protocols/Slack/src/slack_options.cpp43
-rw-r--r--protocols/Slack/src/slack_options.h32
-rw-r--r--protocols/Slack/src/slack_proto.cpp147
-rw-r--r--protocols/Slack/src/slack_proto.h146
-rw-r--r--protocols/Slack/src/slack_requests.cpp95
-rw-r--r--protocols/Slack/src/slack_utils.cpp45
-rw-r--r--protocols/Slack/src/stdafx.cxx18
-rw-r--r--protocols/Slack/src/stdafx.h58
-rw-r--r--protocols/Slack/src/version.h14
24 files changed, 1557 insertions, 0 deletions
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 &param)
+ {
+ Add(param.szName);
+ return *this;
+ }
+
+ HttpUri &operator<<(const INT_VALUE &param)
+ {
+ Add("%s=%i", param.szName, param.iValue);
+ return *this;
+ }
+
+ HttpUri &operator<<(const LONG_VALUE &param)
+ {
+ Add("%s=%lld", param.szName, param.llValue);
+ return *this;
+ }
+
+ HttpUri &operator<<(const CHAR_VALUE &param)
+ {
+ Add("%s=%s", param.szName, param.szValue);
+ return *this;
+ }
+
+ HttpUri &operator<<(const ENCODED_VALUE &param)
+ {
+ 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 &param)
+ {
+ Add(param.szName);
+ return *this;
+ }
+
+ HttpHeaders& operator<<(const CHAR_VALUE &param)
+ {
+ Add(param.szName, param.szValue);
+ return *this;
+ }
+
+ HttpHeaders& operator<<(const ENCODED_VALUE &param)
+ {
+ 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 &param)
+ {
+ Add(param.szName);
+ return *this;
+ }
+
+ HttpContent & operator<<(const INT_VALUE &param)
+ {
+ Add("%s=%i", param.szName, param.iValue);
+ return *this;
+ }
+
+ HttpContent & operator<<(const LONG_VALUE &param)
+ {
+ Add("%s=%lld", param.szName, param.llValue);
+ return *this;
+ }
+
+ HttpContent & operator<<(const CHAR_VALUE &param)
+ {
+ Add("%s=%s", param.szName, param.szValue);
+ return *this;
+ }
+
+ HttpContent &operator<<(const ENCODED_VALUE &param)
+ {
+ 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"