summaryrefslogtreecommitdiff
path: root/protocols/Teams/src/teams_chatrooms.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Teams/src/teams_chatrooms.cpp')
-rw-r--r--protocols/Teams/src/teams_chatrooms.cpp665
1 files changed, 665 insertions, 0 deletions
diff --git a/protocols/Teams/src/teams_chatrooms.cpp b/protocols/Teams/src/teams_chatrooms.cpp
new file mode 100644
index 0000000000..ca0621bed5
--- /dev/null
+++ b/protocols/Teams/src/teams_chatrooms.cpp
@@ -0,0 +1,665 @@
+/*
+Copyright (c) 2025 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+void CTeamsProto::InitGroupChatModule()
+{
+ GCREGISTER gcr = {};
+ gcr.dwFlags = GC_DATABASE | GC_PERSISTENT;
+ gcr.iMaxText = 0;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ Chat_Register(&gcr);
+
+ HookProtoEvent(ME_GC_EVENT, &CTeamsProto::OnGroupChatEventHook);
+ HookProtoEvent(ME_GC_BUILDMENU, &CTeamsProto::OnGroupChatMenuHook);
+
+ CreateProtoService(PS_JOINCHAT, &CTeamsProto::OnJoinChatRoom);
+ CreateProtoService(PS_LEAVECHAT, &CTeamsProto::OnLeaveChatRoom);
+}
+
+SESSION_INFO* CTeamsProto::StartChatRoom(const wchar_t *tid, const wchar_t *tname, const char *pszVersion)
+{
+ // Create the group chat session
+ SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, tid, tname);
+ if (!si)
+ return nullptr;
+
+ bool bFetchInfo = si->arUsers.getCount() == 0;
+ if (pszVersion) {
+ CMStringA oldVersion(getMStringA(si->hContact, "Version"));
+ if (oldVersion != pszVersion)
+ bFetchInfo = true;
+ }
+
+ if (bFetchInfo) {
+ // Create user statuses
+ Chat_AddGroup(si, TranslateT("Admin"));
+ Chat_AddGroup(si, TranslateT("User"));
+
+ GetChatInfo(tid);
+ }
+
+ // Finish initialization
+ Chat_Control(si, (getBool("HideChats", 1) ? WINDOW_HIDDEN : SESSION_INITDONE));
+ Chat_Control(si, SESSION_ONLINE);
+ return si;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Group chat invitation dialog
+
+class CSkypeInviteDlg : public CTeamsDlgBase
+{
+ CCtrlCombo m_combo;
+
+public:
+ MCONTACT m_hContact = 0;
+
+ CSkypeInviteDlg(CTeamsProto *proto) :
+ CTeamsDlgBase(proto, IDD_GC_INVITE),
+ m_combo(this, IDC_CONTACT)
+ {}
+
+ bool OnInitDialog() override
+ {
+ for (auto &hContact : m_proto->AccContacts())
+ if (!m_proto->isChatRoom(hContact))
+ m_combo.AddString(Clist_GetContactDisplayName(hContact), hContact);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ m_hContact = m_combo.GetCurData();
+ return true;
+ }
+};
+
+int CTeamsProto::OnGroupChatEventHook(WPARAM, LPARAM lParam)
+{
+ GCHOOK *gch = (GCHOOK*)lParam;
+ if (!gch)
+ return 0;
+
+ auto *si = gch->si;
+ if (mir_strcmp(si->pszModule, m_szModuleName) != 0)
+ return 0;
+
+ T2Utf chat_id(si->ptszID), user_id(gch->ptszUID);
+
+ switch (gch->iType) {
+ case GC_USER_MESSAGE:
+ SendChatMessage(si, gch->ptszText);
+ break;
+
+ case GC_USER_PRIVMESS:
+ {
+ MCONTACT hContact = FindContact(user_id);
+ if (hContact == NULL) {
+ hContact = AddContact(user_id, T2Utf(gch->ptszNick), true);
+ setWord(hContact, "Status", ID_STATUS_ONLINE);
+ Contact::Hide(hContact);
+ }
+ CallService(MS_MSG_SENDMESSAGEW, hContact, 0);
+ }
+ break;
+
+ case GC_USER_LOGMENU:
+ switch (gch->dwData) {
+ case 10:
+ {
+ CSkypeInviteDlg dlg(this);
+ if (dlg.DoModal())
+ if (dlg.m_hContact != NULL)
+ InviteUserToChat(chat_id, getId(dlg.m_hContact), "User");
+ }
+ break;
+
+ case 20:
+ OnLeaveChatRoom(si->hContact, NULL);
+ break;
+
+ case 30:
+ CMStringW newTopic = ChangeTopicForm();
+ if (!newTopic.IsEmpty())
+ SetChatProperty(chat_id, "topic", T2Utf(newTopic.GetBuffer()));
+ break;
+ }
+ break;
+
+ case GC_USER_NICKLISTMENU:
+ switch (gch->dwData) {
+ case 10:
+ KickChatUser(chat_id, user_id);
+ break;
+ case 30:
+ InviteUserToChat(chat_id, user_id, "Admin");
+ break;
+ case 40:
+ InviteUserToChat(chat_id, user_id, "User");
+ break;
+ case 50:
+ ptrW tnick_old(GetChatContactNick(si, gch->ptszUID, gch->ptszText));
+
+ ENTER_STRING pForm = {};
+ pForm.type = ESF_COMBO;
+ pForm.caption = TranslateT("Enter new nickname");
+ pForm.szModuleName = m_szModuleName;
+ pForm.szDataPrefix = "renamenick_";
+
+ if (EnterString(&pForm)) {
+ if (si->hContact == NULL)
+ break; // This probably shouldn't happen, but if chat is NULL for some reason, do nothing
+
+ ptrW tnick_new(pForm.ptszResult);
+ bool reset = mir_wstrlen(tnick_new) == 0;
+ if (reset) {
+ // User fill blank name, which means we reset the custom nick
+ db_unset(si->hContact, "UsersNicks", user_id);
+ tnick_new = GetChatContactNick(si, gch->ptszUID, gch->ptszText);
+ }
+
+ if (!mir_wstrcmp(tnick_old, tnick_new))
+ break; // New nick is same, do nothing
+
+ GCEVENT gce = { si, GC_EVENT_NICK };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszNick.w = tnick_old;
+ gce.bIsMe = IsMe(user_id);
+ gce.pszUID.w = gch->ptszUID;
+ gce.pszText.w= tnick_new;
+ gce.time = time(0);
+ Chat_Event(&gce);
+
+ if (!reset)
+ db_set_ws(si->hContact, "UsersNicks", user_id, tnick_new);
+ }
+ break;
+ }
+ break;
+ }
+ return 1;
+}
+
+INT_PTR CTeamsProto::OnJoinChatRoom(WPARAM hContact, LPARAM)
+{
+ if (hContact) {
+ ptrW idT(getWStringA(hContact, DBKEY_ID));
+ ptrW nameT(getWStringA(hContact, "Nick"));
+ StartChatRoom(idT, nameT != NULL ? nameT : idT);
+ }
+ return 0;
+}
+
+INT_PTR CTeamsProto::OnLeaveChatRoom(WPARAM hContact, LPARAM)
+{
+ if (!IsOnline())
+ return 1;
+
+ if (hContact && IDYES == MessageBox(nullptr, TranslateT("This chat is going to be destroyed forever with all its contents. This action cannot be undone. Are you sure?"), TranslateT("Warning"), MB_YESNO | MB_ICONQUESTION)) {
+ ptrW idT(getWStringA(hContact, DBKEY_ID));
+ auto *si = Chat_Find(idT, m_szModuleName);
+ Chat_Control(si, SESSION_OFFLINE);
+ Chat_Terminate(si);
+
+ db_delete_contact(hContact, CDF_DEL_CONTACT);
+ }
+ return 0;
+}
+
+/* CHAT EVENT */
+
+bool CTeamsProto::OnChatEvent(const JSONNode &node)
+{
+ CMStringW wszChatId(UrlToSkypeId(node["conversationLink"].as_mstring()));
+ CMStringW szFromId(UrlToSkypeId(node["from"].as_mstring()));
+
+ CMStringW wszTopic(node["threadtopic"].as_mstring());
+ CMStringW wszContent(node["content"].as_mstring());
+
+ SESSION_INFO *si = Chat_Find(wszChatId, m_szModuleName);
+ if (si == nullptr) {
+ si = StartChatRoom(wszChatId, wszTopic);
+ if (si == nullptr) {
+ debugLogW(L"unable to create chat %s", wszChatId.c_str());
+ return true;
+ }
+ }
+
+ std::string messageType = node["messagetype"].as_string();
+ if (messageType == "ThreadActivity/AddMember") {
+ // <addmember><eventtime>1429186229164</eventtime><initiator>8:initiator</initiator><target>8:user</target></addmember>
+ TiXmlDocument doc;
+ if (!doc.Parse(T2Utf(wszContent))) {
+ if (auto *pRoot = doc.FirstChildElement("addmember")) {
+ auto *pszTarget = XmlGetChildText(pRoot, "target");
+ if (!AddChatContact(si, Utf2T(pszTarget), L"User")) {
+ OBJLIST<char> arIds(1);
+ arIds.insert(newStr(pszTarget));
+ GetChatMembers(arIds, si);
+ }
+ }
+ }
+ return true;
+ }
+
+ if (messageType == "ThreadActivity/DeleteMember") {
+ // <deletemember><eventtime>1429186229164</eventtime><initiator>8:initiator</initiator><target>8:user</target></deletemember>
+ TiXmlDocument doc;
+ if (!doc.Parse(T2Utf(wszContent))) {
+ if (auto *pRoot = doc.FirstChildElement("deletemember")) {
+ CMStringW target = Utf2T(XmlGetChildText(pRoot, "target"));
+ CMStringW initiator = Utf2T(XmlGetChildText(pRoot, "initiator"));
+ RemoveChatContact(si, target, initiator);
+ }
+ }
+ return true;
+ }
+
+ if (messageType == "ThreadActivity/TopicUpdate") {
+ // <topicupdate><eventtime>1429532702130</eventtime><initiator>8:user</initiator><value>test topic</value></topicupdate>
+ TiXmlDocument doc;
+ if (!doc.Parse(T2Utf(wszContent))) {
+ if (auto *pRoot = doc.FirstChildElement("topicupdate")) {
+ CMStringW initiator = Utf2T(XmlGetChildText(pRoot, "initiator"));
+ CMStringW value = Utf2T(XmlGetChildText(pRoot, "value"));
+ Chat_ChangeSessionName(si, value);
+
+ GCEVENT gce = { si, GC_EVENT_TOPIC };
+ gce.pszUID.w = initiator;
+ gce.pszNick.w = GetSkypeNick(initiator);
+ gce.pszText.w = wszTopic;
+ Chat_Event(&gce);
+ }
+ }
+ return true;
+ }
+
+ if (messageType == "ThreadActivity/RoleUpdate") {
+ // <roleupdate><eventtime>1429551258363</eventtime><initiator>8:user</initiator><target><id>8:user1</id><role>admin</role></target></roleupdate>
+ TiXmlDocument doc;
+ if (!doc.Parse(T2Utf(wszContent))) {
+ if (auto *pRoot = doc.FirstChildElement("roleupdate")) {
+ CMStringW initiator = Utf2T(UrlToSkypeId(XmlGetChildText(pRoot, "initiator")));
+
+ auto *pTarget = pRoot->FirstChildElement("target");
+ if (pTarget) {
+ CMStringW id = Utf2T(UrlToSkypeId(XmlGetChildText(pTarget, "id")));
+ const char *role = XmlGetChildText(pTarget, "role");
+
+ GCEVENT gce = { si, !mir_strcmpi(role, "Admin") ? GC_EVENT_ADDSTATUS : GC_EVENT_REMOVESTATUS };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszNick.w = id;
+ gce.pszUID.w = id;
+ gce.pszText.w = initiator;
+ gce.time = time(0);
+ gce.bIsMe = IsMe(T2Utf(id));
+ gce.pszStatus.w = TranslateT("Admin");
+ Chat_Event(&gce);
+ }
+ }
+ }
+ return true;
+ }
+
+ // some slack, let's drop it
+ if (messageType == "ThreadActivity/HistoryDisclosedUpdate" || messageType == "ThreadActivity/JoiningEnabledUpdate")
+ return true;
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CTeamsProto::SendChatMessage(SESSION_INFO *si, const wchar_t *tszMessage)
+{
+ if (!IsOnline())
+ return;
+
+ CMStringA szMessage(ptrA(mir_utf8encodeW(tszMessage)));
+ szMessage.TrimRight();
+ bool bRich = AddBbcodes(szMessage);
+
+ JSONNode node;
+ node << INT64_PARAM("clientmessageid", getRandomId()) << CHAR_PARAM("messagetype", bRich ? "RichText" : "Text")
+ << CHAR_PARAM("contenttype", "text") << CHAR_PARAM("content", szMessage);
+ if (strncmp(szMessage, "/me ", 4) == 0)
+ node << INT_PARAM("skypeemoteoffset", 4);
+
+ CMStringA szUrl = "/users/ME/conversations/" + mir_urlEncode(T2Utf(si->ptszID)) + "/messages";
+ AsyncHttpRequest *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_CHATS, szUrl, &CTeamsProto::OnMessageSent);
+ pReq->m_szParam = node.write().c_str();
+ pReq->pUserInfo = new COwnMessage(szMessage);
+ pReq->hContact = si->hContact;
+ PushRequest(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CTeamsProto::GetChatMembers(const LIST<char> &ids, SESSION_INFO *si)
+{
+ auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_CHATS, "/profiles", &CTeamsProto::OnGetChatMembers);
+
+ JSONNode node, mris(JSON_ARRAY); mris.set_name("mris");
+ for (auto &it : ids)
+ mris.push_back(JSONNode("", it));
+ node << mris << CHAR_PARAM("locale", "en-US");
+ pReq->m_szParam = node.write().c_str();
+
+ pReq->pUserInfo = si;
+ PushRequest(pReq);
+};
+
+void CTeamsProto::OnGetChatMembers(MHttpResponse *response, AsyncHttpRequest *pRequest)
+{
+ TeamsReply reply(response);
+ if (reply.error())
+ return;
+
+ auto &root = reply.data();
+ auto *si = (SESSION_INFO *)pRequest->pUserInfo;
+
+ for (auto &it : root["profiles"]) {
+ CMStringW wszUserId(Utf2T(it.name()));
+ if (auto *pUser = g_chatApi.UM_FindUser(si, wszUserId)) {
+ auto &pProfile = it["profile"];
+ if (auto &pName = pProfile["displayName"])
+ replaceStrW(pUser->pszNick, pName.as_mstring());
+ }
+ }
+
+ if (g_chatApi.OnChangeNick)
+ g_chatApi.OnChangeNick(si);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CTeamsProto::OnGetChatInfo(MHttpResponse *response, AsyncHttpRequest*)
+{
+ TeamsReply reply(response);
+ if (reply.error())
+ return;
+
+ auto &root = reply.data();
+ const JSONNode &properties = root["properties"];
+ if (!properties["capabilities"] || properties["capabilities"].empty())
+ return;
+
+ CMStringW wszChatId(UrlToSkypeId(root["messages"].as_mstring()));
+ auto *si = Chat_Find(wszChatId, m_szModuleName);
+ if (si == nullptr)
+ return;
+
+ setString(si->hContact, "Version", root["version"].as_string().c_str());
+
+ OBJLIST<char> arIds(1);
+ for (auto &member : root["members"]) {
+ CMStringW username(UrlToSkypeId(member["userLink"].as_mstring()));
+ CMStringW role = member["role"].as_mstring();
+ if (!AddChatContact(si, username, role, true))
+ arIds.insert(newStr(mir_u2a(username)));
+ }
+
+ if (arIds.getCount())
+ GetChatMembers(arIds, si);
+
+ GetServerHistory(si->hContact, 100, 0, true);
+}
+
+void CTeamsProto::GetChatInfo(const wchar_t *chatId)
+{
+ auto *pReq = new AsyncHttpRequest(REQUEST_GET, HOST_CHATS, 0, &CTeamsProto::OnGetChatInfo);
+ pReq->m_szUrl.AppendFormat("/threads/%S", chatId);
+ pReq << CHAR_PARAM("view", "msnp24Equivalent");
+ PushRequest(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+wchar_t* CTeamsProto::GetChatContactNick(SESSION_INFO *si, const wchar_t *id, const wchar_t *name, bool *isQualified)
+{
+ if (isQualified)
+ *isQualified = true;
+
+ // Check if there's a user with this id in a chat
+ if (auto *pUser = g_chatApi.UM_FindUser(si, id))
+ return mir_wstrdup(pUser->pszNick);
+
+ // Check if there is custom nick for this chat contact
+ if (auto *tname = db_get_wsa(si->hContact, "UsersNicks", T2Utf(id)))
+ return tname;
+
+ // Check if we have this contact in database
+ if (IsMe(id)) {
+ // Return my nick
+ if (auto *tname = getWStringA("Nick"))
+ return tname;
+ }
+ else {
+ MCONTACT hContact = FindContact(id);
+ if (hContact != NULL) {
+ // Primarily return custom name
+ if (auto *tname = db_get_wsa(hContact, "CList", "MyHandle"))
+ return tname;
+
+ // If not exists, then user nick
+ if (auto *tname = getWStringA(hContact, "Nick"))
+ return tname;
+ }
+ }
+
+ if (isQualified)
+ *isQualified = false;
+
+ // Return default value as nick - given name or user id
+ if (name != nullptr)
+ return mir_wstrdup(name);
+ return mir_wstrdup(GetSkypeNick(id));
+}
+
+void CTeamsProto::InviteUserToChat(const char *chatId, const char *skypename, const char *role)
+{
+ JSONNode node;
+ node << CHAR_PARAM("role", role);
+
+ auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_CHATS);
+ pReq->m_szUrl.AppendFormat("/threads/%s/members/%s", chatId, skypename);
+ pReq->m_szParam = node.write().c_str();
+ PushRequest(pReq);
+}
+
+void CTeamsProto::SetChatProperty(const char *chatId, const char *propname, const char *value)
+{
+ JSONNode node;
+ node << CHAR_PARAM(propname, value);
+
+ auto *pReq = new AsyncHttpRequest(REQUEST_PUT, HOST_CHATS);
+ pReq->m_szUrl.AppendFormat("/threads/%s/properties?name=%s", chatId, propname);
+ pReq->m_szParam = node.write().c_str();
+ PushRequest(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CTeamsProto::AddChatContact(SESSION_INFO *si, const wchar_t *id, const wchar_t *role, bool isChange)
+{
+ bool isQualified;
+ ptrW szNick(GetChatContactNick(si, id, 0, &isQualified));
+
+ GCEVENT gce = { si, GC_EVENT_JOIN };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszNick.w = szNick;
+ gce.pszUID.w = id;
+ gce.time = !isChange ? time(0) : NULL;
+ gce.bIsMe = IsMe(id);
+ gce.pszStatus.w = TranslateW(role);
+ Chat_Event(&gce);
+
+ return isQualified;
+}
+
+void CTeamsProto::RemoveChatContact(SESSION_INFO *si, const wchar_t *id, const wchar_t *initiator)
+{
+ if (IsMe(id))
+ return;
+
+ ptrW szNick(GetChatContactNick(si, id));
+ ptrW szInitiator(GetChatContactNick(si, initiator));
+
+ GCEVENT gce = { si, GC_EVENT_KICK };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszNick.w = szNick;
+ gce.pszUID.w = id;
+ gce.time = time(0);
+ gce.bIsMe = IsMe(id);
+ gce.pszStatus.w = szInitiator;
+ Chat_Event(&gce);
+}
+
+void CTeamsProto::KickChatUser(const char *chatId, const char *userId)
+{
+ PushRequest(new AsyncHttpRequest(REQUEST_DELETE, HOST_CHATS, "/threads/" + mir_urlEncode(chatId) + "/members/" + mir_urlEncode(userId)));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Group chat creation dialog
+
+class CSkypeGCCreateDlg : public CTeamsDlgBase
+{
+ CCtrlClc m_clc;
+
+public:
+ OBJLIST<char> m_ContactsList;
+
+ CSkypeGCCreateDlg(CTeamsProto *proto) :
+ CTeamsDlgBase(proto, IDD_GC_CREATE),
+ m_clc(this, IDC_CLIST),
+ m_ContactsList(1)
+ {
+ m_clc.OnListRebuilt = Callback(this, &CSkypeGCCreateDlg::FilterList);
+ }
+
+ ~CSkypeGCCreateDlg()
+ {
+ }
+
+ bool OnInitDialog() override
+ {
+ SetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE,
+ GetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE) | CLS_CHECKBOXES | CLS_HIDEEMPTYGROUPS | CLS_USEGROUPS | CLS_GREYALTERNATE);
+ m_clc.SendMsg(CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP | CLS_EX_TRACKSELECT, 0);
+
+ ResetListOptions(&m_clc);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ for (auto &hContact : m_proto->AccContacts())
+ if (!m_proto->isChatRoom(hContact))
+ if (HANDLE hItem = m_clc.FindContact(hContact))
+ if (m_clc.GetCheck(hItem))
+ m_ContactsList.insert(newStr(m_proto->getId(hContact)));
+
+ m_ContactsList.insert(newStr(m_proto->m_szSkypename));
+ return true;
+ }
+
+ void FilterList(CCtrlClc *)
+ {
+ for (auto &hContact : Contacts()) {
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (mir_strcmp(proto, m_proto->m_szModuleName) || m_proto->isChatRoom(hContact))
+ if (HANDLE hItem = m_clc.FindContact(hContact))
+ m_clc.DeleteItem(hItem);
+ }
+ }
+
+ void ResetListOptions(CCtrlClc *)
+ {
+ m_clc.SetHideEmptyGroups(true);
+ m_clc.SetHideOfflineRoot(true);
+ }
+};
+
+INT_PTR CTeamsProto::SvcCreateChat(WPARAM, LPARAM)
+{
+ if (IsOnline()) {
+ CSkypeGCCreateDlg dlg(this);
+ if (dlg.DoModal()) {
+ JSONNode node;
+ JSONNode members(JSON_ARRAY); members.set_name("members");
+
+ for (auto &it : dlg.m_ContactsList) {
+ JSONNode member;
+ member << CHAR_PARAM("id", it) << CHAR_PARAM("role", !mir_strcmpi(it, m_szSkypename) ? "Admin" : "User");
+ members << member;
+ }
+ node << members;
+
+ auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_CHATS, "/threads");
+ pReq->m_szParam = node.write().c_str();
+ PushRequest(pReq);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* Menus */
+
+int CTeamsProto::OnGroupChatMenuHook(WPARAM, LPARAM lParam)
+{
+ GCMENUITEMS *gcmi = (GCMENUITEMS*)lParam;
+ if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) return 0;
+
+ if (gcmi->Type == MENU_ON_LOG) {
+ static const struct gc_item Items[] =
+ {
+ { LPGENW("&Invite user..."), 10, MENU_ITEM, FALSE },
+ { LPGENW("&Leave chat session"), 20, MENU_ITEM, FALSE },
+ { LPGENW("&Change topic..."), 30, MENU_ITEM, FALSE }
+ };
+ Chat_AddMenuItems(gcmi->hMenu, _countof(Items), Items, &g_plugin);
+ }
+ else if (gcmi->Type == MENU_ON_NICKLIST) {
+ static const struct gc_item Items[] =
+ {
+ { LPGENW("Kick &user"), 10, MENU_ITEM },
+ { nullptr, 0, MENU_SEPARATOR },
+ { LPGENW("Set &role"), 20, MENU_NEWPOPUP },
+ { LPGENW("&Admin"), 30, MENU_POPUPITEM },
+ { LPGENW("&User"), 40, MENU_POPUPITEM },
+ { LPGENW("Change nick..."), 50, MENU_ITEM },
+ };
+ Chat_AddMenuItems(gcmi->hMenu, _countof(Items), Items, &g_plugin);
+ }
+
+ return 0;
+}
+
+CMStringW CTeamsProto::ChangeTopicForm()
+{
+ CMStringW caption(FORMAT, L"[%s] %s", _A2T(m_szModuleName).get(), TranslateT("Enter new chatroom topic"));
+ ENTER_STRING pForm = {};
+ pForm.type = ESF_MULTILINE;
+ pForm.caption = caption;
+ pForm.szModuleName = m_szModuleName;
+ return (!EnterString(&pForm)) ? CMStringW() : CMStringW(ptrW(pForm.ptszResult));
+}