/*
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 .
*/
#include "stdafx.h"
void CSteamProto::SendGetChatsRequest()
{
CChatRoomGetMyChatRoomGroupsRequest request;
WSSendService(GetMyChatRoomGroups, request);
}
void CSteamProto::OnGetMyChats(const CChatRoomGetMyChatRoomGroupsResponse &reply, const CMsgProtoBufHeader &hdr)
{
if (hdr.failed())
return;
std::map chatIds;
for (unsigned i = 0; i < reply.n_chat_room_groups; i++) {
auto *pGroup = reply.chat_room_groups[i]->group_summary;
ProcessGroupChat(pGroup);
for (unsigned k = 0; k < pGroup->n_chat_rooms; k++) {
auto *pChat = pGroup->chat_rooms[k];
CMStringW wszId(FORMAT, L"%lld_%lld", pGroup->chat_group_id, pChat->chat_id);
if (auto *si = Chat_Find(wszId, m_szModuleName))
chatIds[si->hContact] = true;
}
}
// clean garbage
for (auto &cc : AccContacts())
if (Contact::IsGroupChat(cc, m_szModuleName) == GCW_CHATROOM && chatIds.find(cc) == chatIds.end())
db_delete_contact(cc, CDF_DEL_CONTACT);
}
/////////////////////////////////////////////////////////////////////////////////////////
void CSteamProto::OnChatChanged(const ChatRoomClientNotifyChatGroupUserStateChangedNotification &reply, const CMsgProtoBufHeader &hdr)
{
if (hdr.failed())
return;
switch (reply.user_action) {
case ECHAT_ROOM_MEMBER_STATE_CHANGE__k_EChatRoomMemberStateChange_Joined:
ProcessGroupChat(reply.group_summary);
break;
case ECHAT_ROOM_MEMBER_STATE_CHANGE__k_EChatRoomMemberStateChange_Parted:
LeaveGroupChat(reply.chat_group_id);
break;
}
}
void CSteamProto::ProcessGroupChat(const CChatRoomGetChatRoomGroupSummaryResponse *pGroup)
{
CMStringW wszGrpName;
if (pGroup->n_chat_rooms > 1 && pGroup->chat_group_name) {
wszGrpName = CMStringW(m_wszGroupName) + L"\\" + Utf2T(pGroup->chat_group_name);
if (!Clist_GroupExists(wszGrpName))
Clist_GroupCreate(0, wszGrpName);
}
SESSION_INFO *pOwner = 0;
for (unsigned k = 0; k < pGroup->n_chat_rooms; k++) {
std::vector ids;
auto *pChat = pGroup->chat_rooms[k];
CMStringW wszId(FORMAT, L"%lld_%lld", pGroup->chat_group_id, pChat->chat_id);
CMStringW wszTitle(Utf2T(pChat->chat_name));
if (wszTitle.IsEmpty())
wszTitle = Utf2T(pGroup->chat_group_name);
auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, wszTitle);
if (pOwner == 0) {
if (!si->arStatuses.getCount()) {
Chat_AddGroup(si, TranslateT("Owner"));
Chat_AddGroup(si, TranslateT("Participant"));
for (unsigned j = 0; j < pGroup->n_top_members; j++) {
uint64_t iSteamId = AccountIdToSteamId(pGroup->top_members[j]);
CMStringW wszUserId(FORMAT, L"%lld", iSteamId), wszNick;
GCEVENT gce = { si, GC_EVENT_JOIN };
gce.pszUID.w = wszUserId;
if (iSteamId == m_iSteamId) {
gce.bIsMe = true;
wszNick = getMStringW("Nick");
}
else if (MCONTACT hContact = GetContact(iSteamId))
wszNick = Clist_GetContactDisplayName(hContact);
else {
CMStringA szSetting(FORMAT, "UserInfo_%lld", iSteamId);
ptrW szName(g_plugin.getWStringA(szSetting));
if (szName)
wszNick = szName;
else {
ids.push_back(iSteamId);
{
mir_cslock lck(m_csChats);
m_chatContactInfo[iSteamId] = si;
}
wszNick = L"@" + wszUserId;
}
}
gce.pszNick.w = wszNick;
gce.pszStatus.w = (pGroup->top_members[j] == pGroup->accountid_owner) ? TranslateT("Owner") : TranslateT("Participant");
Chat_Event(&gce);
}
}
pOwner = si;
}
else si->pParent = pOwner;
setDword(si->hContact, DBKEY_CHAT_ID, pChat->chat_id);
if (!wszGrpName.IsEmpty())
Clist_SetGroup(si->hContact, wszGrpName);
if (mir_strlen(pGroup->chat_group_tagline)) {
Utf2T wszTopic(pGroup->chat_group_tagline);
Chat_SetStatusbarText(si, wszTopic);
GCEVENT gce = { si, GC_EVENT_TOPIC };
gce.pszText.w = wszTopic;
gce.time = time(0);
Chat_Event(&gce);
}
Chat_Control(si, WINDOW_HIDDEN);
Chat_Control(si, SESSION_ONLINE);
if (!ids.empty())
SendUserInfoRequest(ids);
if (pChat->voice_allowed)
ExtraIcon_SetIcon(hExtraXStatus, si->hContact, Skin_GetIconHandle(SKINICON_OTHER_SOUND));
uint32_t dwLastMsgId = getDword(si->hContact, DBKEY_LASTMSG);
if (pChat->time_last_message > dwLastMsgId)
SendGetChatHistory(si->hContact, dwLastMsgId);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void CSteamProto::OnGotClanInfo(const CMsgClientClanState &reply, const CMsgProtoBufHeader &hdr)
{
if (hdr.failed())
return;
CMStringW wszId(FORMAT, L"%lld", reply.steamid_clan);
auto *si = Chat_Find(wszId, m_szModuleName);
if (si == nullptr) {
si = Chat_NewSession(GCW_SERVER, m_szModuleName, wszId, reply.name_info ? Utf2T(reply.name_info->clan_name) : wszId.c_str());
Chat_Control(si, WINDOW_HIDDEN);
Chat_Control(si, SESSION_ONLINE);
}
GCEVENT gce = { si, GC_EVENT_INFORMATION };
gce.time = time(0);
if (reply.user_counts) {
CMStringW wszText;
auto &C = *reply.user_counts;
if (C.has_members)
wszText.AppendFormat(L"%d %s\r\n", C.members, TranslateT("total members"));
if (C.has_online)
wszText.AppendFormat(L"%d %s\r\n", C.online, TranslateT("online members"));
if (C.has_chatting)
wszText.AppendFormat(L"%d %s\r\n", C.chatting, TranslateT("chatting"));
if (C.has_in_game)
wszText.AppendFormat(L"%d %s\r\n", C.in_game, TranslateT("in game"));
if (!wszText.IsEmpty()) {
gce.pszText.w = wszText;
Chat_Event(&gce);
}
}
for (unsigned n = 0; n < reply.n_announcements; n++) {
auto *E = reply.announcements[n];
Utf2T wszText(E->headline);
gce.time = E->event_time;
gce.pszText.w = wszText;
Chat_Event(&gce);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void CSteamProto::SendGetChatHistory(MCONTACT hContact, uint32_t iLastMsgId)
{
CChatRoomGetMessageHistoryRequest request;
request.chat_group_id = GetId(hContact, DBKEY_STEAM_ID); request.has_chat_group_id = true;
request.chat_id = getDword(hContact, DBKEY_CHAT_ID); request.has_chat_id = true;
request.start_time = iLastMsgId; request.has_start_time = true;
WSSendService(GetChatHistory, request, (void*)hContact);
}
void CSteamProto::OnGetChatHistory(const CChatRoomGetMessageHistoryResponse &reply, const CMsgProtoBufHeader &hdr)
{
if (hdr.failed())
return;
std::vector ids;
if (auto *si = Chat_Find(UINT_PTR(GetRequestInfo(hdr.jobid_target)), m_szModuleName)) {
uint32_t iLastMsg = getDword(si->hContact, DBKEY_LASTMSG);
uint32_t iChatId = getDword(si->hContact, DBKEY_CHAT_ID);
for (int i = (int)reply.n_messages - 1; i >= 0; i--) {
auto *pMsg = reply.messages[i];
if (pMsg->server_timestamp > iLastMsg)
iLastMsg = pMsg->server_timestamp;
// some slack, skip it
if (pMsg->server_message)
continue;
auto iSteamId = AccountIdToSteamId(pMsg->sender);
char szMsgId[100], szUserId[100];
mir_snprintf(szMsgId, "%d_%d", iChatId, pMsg->server_timestamp);
_i64toa(iSteamId, szUserId, 10);
_A2T wszUserId(szUserId);
USERINFO ui = {};
ui.pszUID = wszUserId;
if (!si->getUserList().find(&ui)) {
ids.push_back(iSteamId);
mir_cslock lck(m_csChats);
m_chatContactInfo[iSteamId] = si;
}
CMStringA szText(pMsg->message);
DecodeBbcodes(si, szText);
DB::EventInfo dbei(db_event_getById(m_szModuleName, szMsgId));
dbei.flags |= DBEF_UTF;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.szModule = m_szModuleName;
dbei.cbBlob = szText.GetLength();
replaceStr(dbei.pBlob, szText.Detach());
dbei.iTimestamp = pMsg->server_timestamp;
dbei.szId = szMsgId;
dbei.szUserId = szUserId;
if (dbei.getEvent())
db_event_edit(dbei.getEvent(), &dbei, true);
else
db_event_add(si->hContact, &dbei);
}
setDword(si->hContact, DBKEY_LASTMSG, iLastMsg);
}
if (!ids.empty())
SendUserInfoRequest(ids);
}
/////////////////////////////////////////////////////////////////////////////////////////
void CSteamProto::OnGetChatMessage(const CChatRoomIncomingChatMessageNotification &reply, const CMsgProtoBufHeader &hdr)
{
if (hdr.failed())
return;
std::vector ids;
CMStringW wszId(FORMAT, L"%lld_%lld", reply.chat_group_id, reply.chat_id);
if (auto *si = Chat_Find(wszId, m_szModuleName)) {
char szMsgId[100], szUserId[100];
mir_snprintf(szMsgId, "%lld_%d", reply.chat_id, reply.timestamp);
_i64toa(reply.steamid_sender, szUserId, 10);
CMStringA szText(reply.message);
DecodeBbcodes(si, szText);
_A2T wszUserId(szUserId);
USERINFO ui = {};
ui.pszUID = wszUserId;
if (!si->getUserList().find(&ui)) {
ids.push_back(reply.steamid_sender);
mir_cslock lck(m_csChats);
m_chatContactInfo[reply.steamid_sender] = si;
}
DB::EventInfo dbei(db_event_getById(m_szModuleName, szMsgId));
dbei.flags |= DBEF_UTF;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.szModule = m_szModuleName;
dbei.cbBlob = szText.GetLength();
replaceStr(dbei.pBlob, szText.Detach());
dbei.iTimestamp = reply.timestamp;
dbei.szId = szMsgId;
dbei.szUserId = szUserId;
if (dbei.getEvent())
db_event_edit(dbei.getEvent(), &dbei, true);
else
db_event_add(si->hContact, &dbei);
}
if (!ids.empty())
SendUserInfoRequest(ids);
}
/////////////////////////////////////////////////////////////////////////////////////////
INT_PTR CSteamProto::SvcLeaveChat(WPARAM hContact, LPARAM)
{
CChatRoomLeaveChatRoomGroupRequest request;
if (Contact::IsGroupChat(hContact) == GCW_SERVER)
request.chat_group_id = GetId(hContact, DBKEY_GROUP_ID);
else
request.chat_group_id = GetId(hContact, DBKEY_STEAM_ID);
request.has_chat_group_id = true;
WSSendService(LeaveChatGroup, request);
return 0;
}
void CSteamProto::LeaveGroupChat(int64_t chatGroupId)
{
std::vector ids;
for (auto &cc : AccContacts()) {
if (!Contact::IsGroupChat(cc) || GetId(cc, DBKEY_STEAM_ID) != chatGroupId)
continue;
CMStringW wszId(FORMAT, L"%lld_%d", chatGroupId, getDword(cc, DBKEY_CHAT_ID));
if (auto *si = Chat_Find(wszId, m_szModuleName))
Chat_Terminate(si);
ids.push_back(cc);
}
for (auto &cc: ids)
db_delete_contact(cc, CDF_FROM_SERVER);
}
/////////////////////////////////////////////////////////////////////////////////////////
enum
{
IDM_LEAVE = 1,
};
int CSteamProto::GcEventHook(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 (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) {
CMStringW wszText(gch->ptszText);
wszText.TrimRight();
EncodeBbcodes(si, wszText);
T2Utf szText(wszText);
CChatRoomSendChatMessageRequest request;
request.chat_group_id = _wtoi64(si->ptszID); request.has_chat_group_id = true;
request.chat_id = getDword(si->hContact, DBKEY_CHAT_ID); request.has_chat_id = true;
request.echo_to_sender = request.has_echo_to_sender = true;
request.message = szText;
WSSendService(SendChatMessage, request);
}
break;
case GC_USER_PRIVMESS:
Chat_SendPrivateMessage(gch);
break;
case GC_USER_LOGMENU:
Chat_LogMenu(gch);
break;
case GC_USER_NICKLISTMENU:
break;
}
return 0;
}
void CSteamProto::Chat_SendPrivateMessage(GCHOOK *gch)
{
uint64_t iSteamId = _wtoi64(gch->ptszUID);
MCONTACT hContact = GetContact(iSteamId);
if (!hContact) {
PROTOSEARCHRESULT psr = { sizeof(psr) };
psr.id.w = (wchar_t *)gch->ptszUID;
psr.firstName.w = (wchar_t *)gch->ptszNick;
hContact = AddToList(PALF_TEMPORARY, &psr);
if (hContact == 0)
return;
setWString(hContact, "Nick", gch->ptszNick);
Contact::Hide(hContact);
db_set_dw(hContact, "Ignore", "Mask1", 0);
}
CallService(MS_MSG_SENDMESSAGE, hContact, 0);
}
void CSteamProto::Chat_LogMenu(GCHOOK *gch)
{
switch (gch->dwData) {
case IDM_LEAVE:
SvcLeaveChat(gch->si->hContact, 0);
break;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
static gc_item sttLogListItems[] =
{
{ LPGENW("&Leave chat session"), IDM_LEAVE, MENU_ITEM }
};
int CSteamProto::GcMenuHook(WPARAM, LPARAM lParam)
{
GCMENUITEMS *gcmi = (GCMENUITEMS *)lParam;
if (gcmi == nullptr)
return 0;
if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
return 0;
if (gcmi->Type == MENU_ON_LOG) {
Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin);
}
else if (gcmi->Type == MENU_ON_NICKLIST) {
}
return 0;
}