diff options
-rw-r--r-- | protocols/Teams/Teams.vcxproj | 2 | ||||
-rw-r--r-- | protocols/Teams/Teams.vcxproj.filters | 6 | ||||
-rw-r--r-- | protocols/Teams/src/proto.h | 43 | ||||
-rw-r--r-- | protocols/Teams/src/teams_http.cpp | 138 | ||||
-rw-r--r-- | protocols/Teams/src/teams_login.cpp | 58 | ||||
-rw-r--r-- | protocols/Teams/src/teams_proto.cpp | 52 |
6 files changed, 296 insertions, 3 deletions
diff --git a/protocols/Teams/Teams.vcxproj b/protocols/Teams/Teams.vcxproj index f5faf4cd02..00464f85ee 100644 --- a/protocols/Teams/Teams.vcxproj +++ b/protocols/Teams/Teams.vcxproj @@ -30,6 +30,8 @@ <ClCompile Include="src\stdafx.cxx"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> + <ClCompile Include="src\teams_http.cpp" /> + <ClCompile Include="src\teams_login.cpp" /> <ClCompile Include="src\teams_options.cpp" /> <ClCompile Include="src\teams_proto.cpp" /> <ClInclude Include="src\proto.h" /> diff --git a/protocols/Teams/Teams.vcxproj.filters b/protocols/Teams/Teams.vcxproj.filters index 9f0401d3cc..b30f82a50a 100644 --- a/protocols/Teams/Teams.vcxproj.filters +++ b/protocols/Teams/Teams.vcxproj.filters @@ -13,6 +13,12 @@ <ClCompile Include="src\teams_options.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\teams_login.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\teams_http.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\resource.h"> diff --git a/protocols/Teams/src/proto.h b/protocols/Teams/src/proto.h index d1c3fd7f4f..de6a543bd9 100644 --- a/protocols/Teams/src/proto.h +++ b/protocols/Teams/src/proto.h @@ -1,16 +1,50 @@ -struct HttpRequest : public MTHttpRequest<CTeamsProto> +enum HostType { + HOST_OTHER = 0, + HOST_LOGIN = 1, +}; + +struct AsyncHttpRequest : public MTHttpRequest<CTeamsProto> { - HttpRequest(int iRequestType, const char *pszUrl); + AsyncHttpRequest(int iRequestType, HostType host, const char *pszUrl, MTHttpRequestHandler pFunc = 0); + + HostType m_host; }; class CTeamsProto : public PROTO<CTeamsProto> { friend class COptionsMainDlg; + CMStringA GetTenant(); + + // http queue + bool m_isTerminated = true; + mir_cs m_requestQueueLock; + LIST<AsyncHttpRequest> m_requests; + MEventHandle m_hRequestQueueEvent; + HANDLE m_hRequestQueueThread; + + void __cdecl WorkerThread(void *); + + void StartQueue(); + void StopQueue(); + + MHttpResponse* DoSend(AsyncHttpRequest *request); + + void Execute(AsyncHttpRequest *request); + void PushRequest(AsyncHttpRequest *request); + + // login + void Login(); + void RefreshToken(const char *pszScope, AsyncHttpRequest::MTHttpRequestHandler pFunc); + + void OnRefreshAccessToken(MHttpResponse *response, AsyncHttpRequest *pRequest); + void OnRefreshSubstrate(MHttpResponse *response, AsyncHttpRequest *pRequest); + // options int __cdecl OnOptionsInit(WPARAM, LPARAM); - CMOption<wchar_t*> m_wstrCListGroup; + // settings + CMOption<wchar_t *> m_wstrCListGroup; public: // constructor @@ -18,6 +52,9 @@ public: ~CTeamsProto(); MWindow OnCreateAccMgrUI(MWindow) override; + + INT_PTR GetCaps(int type, MCONTACT) override; + int SetStatus(int iNewStatus) override; }; typedef CProtoDlgBase<CTeamsProto> CTeamsDlgBase; diff --git a/protocols/Teams/src/teams_http.cpp b/protocols/Teams/src/teams_http.cpp new file mode 100644 index 0000000000..67bbd78bd4 --- /dev/null +++ b/protocols/Teams/src/teams_http.cpp @@ -0,0 +1,138 @@ +/* +Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +AsyncHttpRequest::AsyncHttpRequest(int type, HostType host, LPCSTR url, MTHttpRequestHandler pFunc) : + m_host(host) +{ + switch (host) { + case HOST_LOGIN: m_szUrl = "login.microsoftonline.com"; break; + } + + AddHeader("User-Agent", NETLIB_USER_AGENT); + + if (url) + m_szUrl.Append(url); + m_pFunc = pFunc; + flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_DUMPASTEXT; + requestType = type; +} +/* +void AsyncHttpRequest::AddAuthentication(CTeamsProto *ppro) +{ + AddHeader("Authentication", CMStringA("skypetoken=") + ppro->m_szApiToken); +} + +void AsyncHttpRequest::AddRegister(CTeamsProto *ppro) +{ + AddHeader("RegistrationToken", CMStringA("registrationToken=") + ppro->m_szToken); +} +*/ + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::StartQueue() +{ + if (!m_isTerminated) + return; + + m_isTerminated = false; + if (m_hRequestQueueThread == nullptr) + ForkThread(&CTeamsProto::WorkerThread); +} + +void CTeamsProto::StopQueue() +{ + m_isTerminated = true; + + if (m_hRequestQueueThread) + m_hRequestQueueEvent.Set(); +} + +void CTeamsProto::PushRequest(AsyncHttpRequest *request) +{ + if (m_isTerminated) + return; + { + mir_cslock lock(m_requestQueueLock); + m_requests.insert(request); + } + m_hRequestQueueEvent.Set(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +MHttpResponse* CTeamsProto::DoSend(AsyncHttpRequest *pReq) +{ + if (pReq->m_host != HOST_OTHER) + pReq->m_szUrl.Insert(0, ((pReq->flags & NLHRF_SSL) ? "https://" : "http://")); + + if (!pReq->m_szParam.IsEmpty()) { + switch (pReq->requestType) { + case REQUEST_PUT: + case REQUEST_POST: + if (!pReq->FindHeader("Content-Type")) { + if (pReq->m_szParam[0] == '[' || pReq->m_szParam[0] == '{') + pReq->AddHeader("Content-Type", "application/json; charset=UTF-8"); + else + pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded"); + } + } + } + + debugLogA("Send request to %s", pReq->m_szUrl.c_str()); + + return Netlib_HttpTransaction(m_hNetlibUser, pReq); +} + +void CTeamsProto::Execute(AsyncHttpRequest *item) +{ + NLHR_PTR response(DoSend(item)); + if (item->m_pFunc != nullptr) + (this->*item->m_pFunc)(response, item); + m_requests.remove(item); + delete item; +} + +void CTeamsProto::WorkerThread(void*) +{ + m_hRequestQueueThread = GetCurrentThread(); + + while (true) { + m_hRequestQueueEvent.Wait(); + if (m_isTerminated) + break; + + while (true) { + AsyncHttpRequest *item = nullptr; + { + mir_cslock lock(m_requestQueueLock); + + if (m_requests.getCount() == 0) + break; + + item = m_requests[0]; + m_requests.remove(0); + } + if (item != nullptr) + Execute(item); + } + } + + m_hRequestQueueThread = nullptr; +} diff --git a/protocols/Teams/src/teams_login.cpp b/protocols/Teams/src/teams_login.cpp new file mode 100644 index 0000000000..9be1df5de8 --- /dev/null +++ b/protocols/Teams/src/teams_login.cpp @@ -0,0 +1,58 @@ +/* +Copyright (C) 2012-25 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +CMStringA CTeamsProto::GetTenant() +{ + CMStringA ret(getMStringA("Tenant")); + return (ret.IsEmpty()) ? "consumers" : ret; +} + +void CTeamsProto::Login() +{ + CMStringA szLogin(getMStringA("Login")), szPassword(getMStringA("Password")); + if (szLogin.IsEmpty() || szPassword.IsEmpty()) { + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + return; + } + + // login + int oldStatus = m_iStatus; + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + StartQueue(); + + RefreshToken("service::api.fl.teams.microsoft.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); + // RefreshToken("service::api.fl.spaces.skype.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); + // RefreshToken("https://substrate.office.com/M365.Access openid profile offline_access", &CTeamsProto::OnRefreshSubstrate); +} + +void CTeamsProto::RefreshToken(const char *pszScope, AsyncHttpRequest::MTHttpRequestHandler pFunc) +{ + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_LOGIN, "/" + GetTenant() + "/oauth2/v2.0/token", pFunc); + pReq << CHAR_PARAM("scope", pszScope) << CHAR_PARAM("client_id", "8ec6bc83-69c8-4392-8f08-b3c986009232") + << CHAR_PARAM("grant_type", "refresh_token") << CHAR_PARAM("refresh_token", getMStringA("Password")); + PushRequest(pReq); +} + +void CTeamsProto::OnRefreshAccessToken(MHttpResponse *response, AsyncHttpRequest *pRequest) +{} + +void CTeamsProto::OnRefreshSubstrate(MHttpResponse *response, AsyncHttpRequest *pRequest) +{} diff --git a/protocols/Teams/src/teams_proto.cpp b/protocols/Teams/src/teams_proto.cpp index b96ebd2593..0cf9fb0e1c 100644 --- a/protocols/Teams/src/teams_proto.cpp +++ b/protocols/Teams/src/teams_proto.cpp @@ -2,6 +2,7 @@ CTeamsProto::CTeamsProto(const char *protoName, const wchar_t *userName) : PROTO<CTeamsProto>(protoName, userName), + m_requests(10), m_wstrCListGroup(this, "DefaultGroup", L"Teams") { HookProtoEvent(ME_OPT_INITIALISE, &CTeamsProto::OnOptionsInit); @@ -17,3 +18,54 @@ CTeamsProto::CTeamsProto(const char *protoName, const wchar_t *userName) : CTeamsProto::~CTeamsProto() { } + +INT_PTR CTeamsProto::GetCaps(int type, MCONTACT) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_AUTHREQ | PF1_CHAT | PF1_BASICSEARCH | PF1_MODEMSG | PF1_FILE | PF1_SERVERCLIST; + case PFLAGNUM_2: + return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_HEAVYDND; + case PFLAGNUM_3: + return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_HEAVYDND; + case PFLAGNUM_4: + return PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SERVERMSGID | PF4_SERVERFORMATTING; + case PFLAG_UNIQUEIDTEXT: + return (INT_PTR)TranslateT("Teams ID"); + } + return 0; +} + +int CTeamsProto::SetStatus(int iNewStatus) +{ + if (iNewStatus == m_iDesiredStatus) + return 0; + + switch (iNewStatus) { + case ID_STATUS_FREECHAT: iNewStatus = ID_STATUS_ONLINE; break; + case ID_STATUS_NA: iNewStatus = ID_STATUS_AWAY; break; + case ID_STATUS_OCCUPIED: iNewStatus = ID_STATUS_DND; 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) { + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + StopQueue(); + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, ID_STATUS_OFFLINE); + + if (!Miranda_IsTerminated()) + setAllContactStatuses(ID_STATUS_OFFLINE, false); + return 0; + } + + if (m_iStatus == ID_STATUS_OFFLINE) + Login(); + // else + // PushRequest(new SetStatusRequest(MirandaToSkypeStatus(m_iDesiredStatus))); + return 0; +} |