/* Copyright © 2016-22 Miranda NG team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "stdafx.h" enum { IDM_CANCEL, IDM_COPY_ID, IDM_CHANGENICK, IDM_CHANGETOPIC, IDM_RENAME, IDM_PASSOWNER, IDM_DESTROY, IDM_LEAVE, IDM_KICK, IDM_INVITE, IDM_ADD }; static void sttDisableMenuItem(int nItems, gc_item *items, uint32_t id, bool disabled) { for (int i = 0; i < nItems; i++) if (items[i].dwID == id) items[i].bDisabled = disabled; } static void sttShowGcMenuItem(int nItems, gc_item *items, uint32_t id, int type) { for (int i = 0; i < nItems; i++) if (items[i].dwID == id) items[i].uType = type; } ///////////////////////////////////////////////////////////////////////////////////////// static int SortRolesByPosition(const CDiscordRole *p1, const CDiscordRole *p2) { return p1->position - p2->position; } void BuildStatusList(const CDiscordGuild *pGuild, SESSION_INFO *si) { Chat_AddGroup(si, L"@owner"); LIST roles(pGuild->arRoles.getCount(), SortRolesByPosition); for (auto &it : pGuild->arRoles) roles.insert(it); for (auto &it : roles) Chat_AddGroup(si, it->wszName); } ///////////////////////////////////////////////////////////////////////////////////////// static gc_item sttLogListItems[] = { { LPGENW("Change &nickname"), IDM_CHANGENICK, MENU_ITEM }, { LPGENW("Invite users"), IDM_INVITE, MENU_ITEM }, { LPGENW("Channel control"), FALSE, MENU_NEWPOPUP }, { LPGENW("Change &topic"), IDM_CHANGETOPIC, MENU_POPUPITEM }, { LPGENW("&Rename channel"), IDM_RENAME, MENU_POPUPITEM }, { nullptr, 0, MENU_POPUPSEPARATOR }, { LPGENW("Destroy channel"), IDM_DESTROY, MENU_POPUPITEM }, { LPGENW("Leave channel"), IDM_LEAVE, MENU_POPUPITEM }, }; static gc_item sttNicklistItems[] = { { LPGENW("Copy ID"), IDM_COPY_ID, MENU_ITEM }, { LPGENW("Add friend"), IDM_ADD, MENU_ITEM }, { nullptr, 0, MENU_SEPARATOR }, { LPGENW("Kick user"), IDM_KICK, MENU_ITEM }, { LPGENW("Make group owner"), IDM_PASSOWNER, MENU_ITEM }, }; int CDiscordProto::GroupchatMenuHook(WPARAM, LPARAM lParam) { GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; if (gcmi == nullptr) return 0; if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) return 0; CDiscordUser *pChat = FindUserByChannel(_wtoi64(gcmi->pszID)); if (pChat == nullptr || pChat->si == nullptr) return 0; bool isOwner = getId(pChat->hContact, DB_KEY_OWNERID) == m_ownId; if (gcmi->Type == MENU_ON_LOG) { if (pChat->pGuild == nullptr) sttShowGcMenuItem(_countof(sttLogListItems), sttLogListItems, IDM_CHANGENICK, 0); sttShowGcMenuItem(_countof(sttLogListItems), sttLogListItems, IDM_LEAVE, isOwner ? 0 : MENU_POPUPITEM); sttShowGcMenuItem(_countof(sttLogListItems), sttLogListItems, IDM_DESTROY, isOwner ? MENU_POPUPITEM : 0); Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin); } else if (gcmi->Type == MENU_ON_NICKLIST) { SnowFlake userId = (gcmi->pszUID) ? _wtoi64(gcmi->pszUID) : 0; bool isFriend = (userId == m_ownId) ? true : FindUser(userId) != 0; sttDisableMenuItem(_countof(sttNicklistItems), sttNicklistItems, IDM_ADD, isFriend); sttDisableMenuItem(_countof(sttNicklistItems), sttNicklistItems, IDM_KICK, !isOwner); sttDisableMenuItem(_countof(sttNicklistItems), sttNicklistItems, IDM_PASSOWNER, !isOwner); Chat_AddMenuItems(gcmi->hMenu, _countof(sttNicklistItems), sttNicklistItems, &g_plugin); } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// void CDiscordProto::Chat_SendPrivateMessage(GCHOOK *gch) { SnowFlake userId = _wtoi64(gch->ptszUID); MCONTACT hContact; CDiscordUser *pUser = FindUser(userId); if (pUser == nullptr) { PROTOSEARCHRESULT psr = { sizeof(psr) }; psr.id.w = (wchar_t*)gch->ptszUID; psr.nick.w = (wchar_t*)gch->ptszNick; if ((hContact = AddToList(PALF_TEMPORARY, &psr)) == 0) return; setId(hContact, DB_KEY_ID, userId); setId(hContact, DB_KEY_CHANNELID, _wtoi64(gch->si->ptszID)); setWString(hContact, DB_KEY_NICK, gch->ptszNick); Contact::Hide(hContact); db_set_dw(hContact, "Ignore", "Mask1", 0); } else hContact = pUser->hContact; CallService(MS_MSG_SENDMESSAGE, hContact, 0); } ///////////////////////////////////////////////////////////////////////////////////////// // Invitation dialog class CGroupchatInviteDlg : public CDiscordDlgBase { CCtrlClc m_clc; SnowFlake m_iChatId; 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); } public: CGroupchatInviteDlg(CDiscordProto *ppro, SnowFlake chatId) : CDiscordDlgBase(ppro, IDD_GROUPCHAT_INVITE), m_clc(this, IDC_CLIST), m_iChatId(chatId) { m_clc.OnNewContact = m_clc.OnListRebuilt = Callback(this, &CGroupchatInviteDlg::FilterList); m_clc.OnOptionsChanged = Callback(this, &CGroupchatInviteDlg::ResetListOptions); } bool OnInitDialog() override { SetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE, GetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE) | CLS_SHOWHIDDEN | CLS_CHECKBOXES | CLS_HIDEEMPTYGROUPS | CLS_USEGROUPS | CLS_GREYALTERNATE | CLS_GROUPCHECKBOXES); m_clc.SendMsg(CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP | CLS_EX_TRACKSELECT, 0); ResetListOptions(&m_clc); FilterList(&m_clc); return true; } bool OnApply() override { // invite users from roster for (auto &hContact : m_proto->AccContacts()) { if (m_proto->isChatRoom(hContact)) continue; if (HANDLE hItem = m_clc.FindContact(hContact)) { if (m_clc.GetCheck(hItem)) { CMStringA szUrl(FORMAT, "/channels/%lld/recipients/%lld", m_iChatId, m_proto->getId(hContact, DB_KEY_ID)); m_proto->Push(new AsyncHttpRequest(m_proto, REQUEST_PUT, szUrl, 0)); } } } return true; } }; ///////////////////////////////////////////////////////////////////////////////////////// // Log menu void CDiscordProto::LeaveChat(CDiscordUser *pChat) { CMStringA szUrl(FORMAT, "/channels/%S?silent=false", pChat->wszUsername.c_str()); Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, nullptr)); } INT_PTR CDiscordProto::SvcLeaveChat(WPARAM hContact, LPARAM) { if (auto *pUser = FindUserByChannel(getId(hContact, DB_KEY_CHANNELID))) if (pUser->si) LeaveChat(pUser); return 0; } void CDiscordProto::Chat_ProcessLogMenu(GCHOOK *gch) { CDiscordUser *pUser = FindUserByChannel(_wtoi64(gch->si->ptszID)); if (pUser == nullptr) return; ENTER_STRING es = {}; es.szModuleName = m_szModuleName; switch (gch->dwData) { case IDM_DESTROY: if (IDYES == MessageBox(nullptr, TranslateT("Do you really want to destroy this channel? This action is non-revertable."), m_tszUserName, MB_YESNO | MB_ICONQUESTION)) LeaveChat(pUser); break; case IDM_LEAVE: LeaveChat(pUser); break; case IDM_RENAME: es.caption = TranslateT("Enter new channel name:"); es.type = ESF_COMBO; es.szDataPrefix = "chat_rename"; if (EnterString(&es)) { JSONNode root; root << WCHAR_PARAM("name", es.ptszResult); CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); mir_free(es.ptszResult); } break; case IDM_CHANGETOPIC: es.caption = TranslateT("Enter new topic:"); es.type = ESF_RICHEDIT; es.szDataPrefix = "chat_topic"; if (EnterString(&es)) { JSONNode root; root << WCHAR_PARAM("topic", es.ptszResult); CMStringA szUrl(FORMAT, "/channels/%S", pUser->wszUsername.c_str()); Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); mir_free(es.ptszResult); } break; case IDM_CHANGENICK: es.caption = TranslateT("Enter your new nick name:"); es.type = ESF_COMBO; es.szDataPrefix = "chat_nick"; es.recentCount = 5; if (EnterString(&es)) { JSONNode root; root << WCHAR_PARAM("nick", es.ptszResult); CMStringA szUrl(FORMAT, "/guilds/%lld/members/@me/nick", pUser->pGuild->m_id); Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, nullptr, &root)); mir_free(es.ptszResult); } break; case IDM_INVITE: CGroupchatInviteDlg dlg(this, pUser->channelId); if (gch->si->pDlg) dlg.SetParent(gch->si->pDlg->GetHwnd()); dlg.DoModal(); break; } } ///////////////////////////////////////////////////////////////////////////////////////// // Nick list menu void CDiscordProto::KickChatUser(CDiscordUser *pChat, const wchar_t *pszUID) { CMStringA szUrl(FORMAT, "/channels/%lld/recipients/%S", pChat->channelId, pszUID); Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, 0)); } void CDiscordProto::MakeChatOwner(CDiscordUser *pChat, const wchar_t *pszUID) { JSONNode payload; payload << WCHAR_PARAM("owner", pszUID); CMStringA szUrl(FORMAT, "/channels/%lld", pChat->channelId); Push(new AsyncHttpRequest(this, REQUEST_PATCH, szUrl, 0, &payload)); } void CDiscordProto::Chat_ProcessNickMenu(GCHOOK* gch) { auto *pChannel = FindUserByChannel(_wtoi64(gch->si->ptszID)); if (pChannel == nullptr) return; switch (gch->dwData) { case IDM_COPY_ID: CopyId(gch->ptszUID); break; case IDM_ADD: AddFriend(_wtoi64(gch->ptszUID)); break; case IDM_KICK: KickChatUser(pChannel, gch->ptszUID); break; case IDM_PASSOWNER: MakeChatOwner(pChannel, gch->ptszUID); break; } } ///////////////////////////////////////////////////////////////////////////////////////// int CDiscordProto::GroupchatEventHook(WPARAM, LPARAM lParam) { GCHOOK *gch = (GCHOOK*)lParam; if (gch == nullptr) return 0; auto *si = gch->si; if (mir_strcmpi(si->pszModule, m_szModuleName)) return 0; switch (gch->iType) { case GC_USER_MESSAGE: if (m_bOnline && mir_wstrlen(gch->ptszText) > 0) { CMStringW wszText(gch->ptszText); wszText.TrimRight(); int pos = wszText.Find(':'); if (pos != -1) { auto wszWord = wszText.Left(pos); wszWord.Trim(); USERINFO *pUser = nullptr; for (auto &U : si->getUserList()) if (wszWord == U->pszNick) { pUser = U; break; } if (pUser) { wszText.Delete(0, pos); wszText.Insert(0, L"<@" + CMStringW(pUser->pszUID) + L">"); } } SendMsg(si->hContact, (si->pDlg) ? si->pDlg->m_hQuoteEvent : 0, T2Utf(wszText)); } break; case GC_USER_PRIVMESS: Chat_SendPrivateMessage(gch); break; case GC_USER_LOGMENU: Chat_ProcessLogMenu(gch); break; case GC_USER_NICKLISTMENU: Chat_ProcessNickMenu(gch); break; case GC_USER_TYPNOTIFY: UserIsTyping(gch->si->hContact, (int)gch->dwData); break; } return 1; }