/*
Copyright © 2016-18 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"
int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2);
static int compareRoles(const CDiscordRole *p1, const CDiscordRole *p2)
{
return compareInt64(p1->id, p2->id);
}
static int compareChatUsers(const CDiscordGuildMember *p1, const CDiscordGuildMember *p2)
{
return compareInt64(p1->userId, p2->userId);
}
CDiscordGuild::CDiscordGuild(SnowFlake _id)
: id(_id),
arChannels(10, compareUsers),
arChatUsers(30, compareChatUsers),
arRoles(10, compareRoles)
{
}
CDiscordGuild::~CDiscordGuild()
{
}
CDiscordUser::~CDiscordUser()
{
if (pGuild != nullptr)
pGuild->arChannels.remove(this);
}
/////////////////////////////////////////////////////////////////////////////////////////
// reads a role from json
void CDiscordProto::ProcessRole(CDiscordGuild *guild, const JSONNode &role)
{
SnowFlake id = ::getId(role["id"]);
CDiscordRole *p = guild->arRoles.find((CDiscordRole*)&id);
if (p == nullptr) {
p = new CDiscordRole();
p->id = id;
guild->arRoles.insert(p);
}
p->color = role["color"].as_int();
p->position = role["position"].as_int();
p->permissions = role["permissions"].as_int();
p->wszName = role["name"].as_mstring();
}
/////////////////////////////////////////////////////////////////////////////////////////
static void sttSetGroupName(MCONTACT hContact, const wchar_t *pwszGroupName)
{
ptrW wszOldName(db_get_wsa(hContact, "CList", "Group"));
if (wszOldName != nullptr) {
ptrW wszChatGroup(Chat_GetGroup());
if (mir_wstrcmpi(wszOldName, wszChatGroup))
return; // custom group, don't touch it
}
db_set_ws(hContact, "CList", "Group", pwszGroupName);
}
void CDiscordProto::BatchChatCreate(void *param)
{
CDiscordGuild *pGuild = (CDiscordGuild*)param;
for (auto &it : pGuild->arChannels)
if (!it->bIsPrivate && !it->bIsGroup)
CreateChat(pGuild, it);
}
void CDiscordProto::CreateChat(CDiscordGuild *pGuild, CDiscordUser *pUser)
{
SESSION_INFO *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, pUser->wszUsername, pUser->wszChannelName);
si->pParent = pGuild->pParentSi;
pUser->hContact = si->hContact;
if (m_bUseGuildGroups) {
if (pUser->parentId) {
CDiscordUser *pParent = FindUserByChannel(pUser->parentId);
if (pParent != nullptr)
sttSetGroupName(pUser->hContact, pParent->wszChannelName);
}
else sttSetGroupName(pUser->hContact, Clist_GroupGetName(pGuild->groupId));
}
BuildStatusList(pGuild, si);
Chat_Control(m_szModuleName, pUser->wszUsername, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
Chat_Control(m_szModuleName, pUser->wszUsername, SESSION_ONLINE);
if (!pUser->wszTopic.IsEmpty()) {
Chat_SetStatusbarText(m_szModuleName, pUser->wszUsername, pUser->wszTopic);
GCEVENT gce = { m_szModuleName, pUser->wszUsername, GC_EVENT_TOPIC };
gce.time = time(0);
gce.ptszText = pUser->wszTopic;
Chat_Event(&gce);
}
}
void CDiscordProto::ProcessGuild(const JSONNode &p)
{
SnowFlake guildId = ::getId(p["id"]);
CDiscordGuild *pGuild = FindGuild(guildId);
if (pGuild == nullptr) {
pGuild = new CDiscordGuild(guildId);
arGuilds.insert(pGuild);
}
if (!pGuild->bSynced)
GatewaySendGuildInfo(guildId);
pGuild->ownerId = ::getId(p["owner_id"]);
pGuild->wszName = p["name"].as_mstring();
if (m_bUseGuildGroups)
pGuild->groupId = Clist_GroupCreate(Clist_GroupExists(m_wszDefaultGroup), pGuild->wszName);
SESSION_INFO *si = Chat_NewSession(GCW_SERVER, m_szModuleName, pGuild->wszName, pGuild->wszName, pGuild);
pGuild->pParentSi = (SESSION_INFO*)si;
pGuild->hContact = si->hContact;
setId(si->hContact, DB_KEY_CHANNELID, guildId);
Chat_Control(m_szModuleName, pGuild->wszName, WINDOW_HIDDEN);
Chat_Control(m_szModuleName, pGuild->wszName, SESSION_ONLINE);
const JSONNode &roles = p["roles"];
for (auto itr = roles.begin(); itr != roles.end(); ++itr)
ProcessRole(pGuild, *itr);
BuildStatusList(pGuild, si);
for (auto &it : pGuild->arChatUsers)
AddGuildUser(pGuild, *it);
const JSONNode &channels = p["channels"];
for (auto itc = channels.begin(); itc != channels.end(); ++itc)
ProcessGuildChannel(pGuild, *itc);
ForkThread(&CDiscordProto::BatchChatCreate, pGuild);
}
/////////////////////////////////////////////////////////////////////////////////////////
CDiscordUser* CDiscordProto::ProcessGuildChannel(CDiscordGuild *pGuild, const JSONNode &pch)
{
CMStringW wszChannelId = pch["id"].as_mstring();
SnowFlake channelId = _wtoi64(wszChannelId);
CMStringW wszName = pch["name"].as_mstring();
CDiscordUser *pUser;
// filter our all channels but the text ones
switch (pch["type"].as_int()) {
case 4: // channel group
if (!m_bUseGuildGroups) // ignore groups when they aren't enabled
return nullptr;
pUser = FindUserByChannel(channelId);
if (pUser == nullptr) {
// missing channel - create it
pUser = new CDiscordUser(channelId);
pUser->bIsPrivate = false;
pUser->channelId = channelId;
pUser->bIsGroup = true;
arUsers.insert(pUser);
pGuild->arChannels.insert(pUser);
MGROUP grpId = Clist_GroupCreate(pGuild->groupId, wszName);
pUser->wszChannelName = Clist_GroupGetName(grpId);
}
return pUser;
case 0: // text channel
pUser = FindUserByChannel(channelId);
if (pUser == nullptr) {
// missing channel - create it
pUser = new CDiscordUser(channelId);
pUser->bIsPrivate = false;
pUser->channelId = channelId;
arUsers.insert(pUser);
}
if (pGuild->arChannels.find(pUser) == nullptr)
pGuild->arChannels.insert(pUser);
pUser->wszUsername = wszChannelId;
if (m_bUseGuildGroups)
pUser->wszChannelName = L"#" + wszName;
else
pUser->wszChannelName = pGuild->wszName + L"#" + wszName;
pUser->wszTopic = pch["topic"].as_mstring();
pUser->pGuild = pGuild;
pUser->lastMsgId = ::getId(pch["last_message_id"]);
pUser->parentId = _wtoi64(pch["parent_id"].as_mstring());
setId(pUser->hContact, DB_KEY_ID, channelId);
setId(pUser->hContact, DB_KEY_CHANNELID, channelId);
return pUser;
}
return nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////
void CDiscordProto::AddGuildUser(CDiscordGuild *pGuild, const CDiscordGuildMember &pUser)
{
int flags = 0;
switch (pUser.iStatus) {
case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_DND:
flags = 1;
break;
}
auto *pStatus = g_chatApi.TM_FindStatus(pGuild->pParentSi->pStatuses, pUser.wszRole);
wchar_t wszUserId[100];
_i64tow_s(pUser.userId, wszUserId, _countof(wszUserId), 10);
g_chatApi.UM_AddUser(pGuild->pParentSi, wszUserId, pUser.wszNick, (pStatus) ? pStatus->iStatus : 0)->iStatusEx = flags;
}
/////////////////////////////////////////////////////////////////////////////////////////
void CDiscordProto::ParseGuildContents(CDiscordGuild *pGuild, const JSONNode &pRoot)
{
LIST newMembers(100);
// store all guild members
const JSONNode &pMembers = pRoot["members"];
for (auto it = pMembers.begin(); it != pMembers.end(); ++it) {
const JSONNode &m = *it;
CMStringW wszUserId = m["user"]["id"].as_mstring();
SnowFlake userId = _wtoi64(wszUserId);
CDiscordGuildMember *pm = pGuild->FindUser(userId);
if (pm == nullptr) {
pm = new CDiscordGuildMember(userId);
pGuild->arChatUsers.insert(pm);
newMembers.insert(pm);
}
pm->wszNick = m["nick"].as_mstring();
if (pm->wszNick.IsEmpty())
pm->wszNick = m["user"]["username"].as_mstring() + L"#" + m["user"]["discriminator"].as_mstring();
if (userId == pGuild->ownerId)
pm->wszRole = L"@owner";
else {
CDiscordRole *pRole = nullptr;
const JSONNode &pRoles = m["roles"];
for (auto itr = pRoles.begin(); itr != pRoles.end(); ++itr) {
SnowFlake roleId = ::getId(*itr);
if (pRole = pGuild->arRoles.find((CDiscordRole*)&roleId))
break;
}
pm->wszRole = (pRole == nullptr) ? L"@everyone" : pRole->wszName;
}
pm->iStatus = ID_STATUS_OFFLINE;
}
// parse online statuses
const JSONNode &pStatuses = pRoot["presences"];
for (auto it = pStatuses.begin(); it != pStatuses.end(); ++it) {
const JSONNode &s = *it;
CDiscordGuildMember *gm = pGuild->FindUser(::getId(s["user"]["id"]));
if (gm != nullptr)
gm->iStatus = StrToStatus(s["status"].as_mstring());
}
for (auto &pm : newMembers)
AddGuildUser(pGuild, *pm);
// retrieve missing histories
for (auto &it : pGuild->arChannels) {
if (it->bIsPrivate)
continue;
SnowFlake oldMsgId = getId(it->hContact, DB_KEY_LASTMSGID);
if (oldMsgId != 0 && it->lastMsgId > oldMsgId)
RetrieveHistory(it->hContact, MSG_AFTER, oldMsgId, 99);
}
pGuild->bSynced = true;
}