/*
Facebook plugin for Miranda Instant Messenger
_____________________________________________
Copyright © 2011-17 Robert Pösel
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"
#include
#include
void FacebookProto::UpdateChat(const char *chat_id, const char *id, const char *name, const char *message, DWORD timestamp, bool is_old)
{
// replace % to %% to not interfere with chat color codes
std::string smessage = message;
utils::text::replace_all(&smessage, "%", "%%");
ptrW tid(mir_a2u(id));
ptrW tnick(mir_a2u_cp(name, CP_UTF8));
ptrW ttext(mir_a2u_cp(smessage.c_str(), CP_UTF8));
ptrW tchat_id(mir_a2u(chat_id));
GCEVENT gce = { m_szModuleName, tchat_id, GC_EVENT_MESSAGE };
gce.ptszText = ttext;
gce.time = timestamp ? timestamp : ::time(nullptr);
if (id != nullptr)
gce.bIsMe = !mir_strcmp(id, facy.self_.user_id.c_str());
gce.dwFlags |= GCEF_ADDTOLOG;
if (is_old) {
gce.dwFlags |= GCEF_NOTNOTIFY;
gce.dwFlags &= ~GCEF_ADDTOLOG;
}
gce.ptszNick = tnick;
gce.ptszUID = tid;
Chat_Event(&gce);
facy.erase_reader(ChatIDToHContact(chat_id));
}
void FacebookProto::RenameChat(const char *chat_id, const char *name)
{
ptrW tchat_id(mir_a2u(chat_id));
ptrW tname(mir_a2u_cp(name, CP_UTF8));
Chat_ChangeSessionName(m_szModuleName, tchat_id, tname);
}
int FacebookProto::OnGCEvent(WPARAM, LPARAM lParam)
{
GCHOOK *hook = reinterpret_cast(lParam);
if (mir_strcmp(hook->pszModule, m_szModuleName))
return 0;
// Ignore for special chatrooms
if (!mir_wstrcmp(hook->ptszID, _A2W(FACEBOOK_NOTIFICATIONS_CHATROOM)))
return 0;
switch (hook->iType) {
case GC_USER_MESSAGE:
{
std::string msg = _T2A(hook->ptszText, CP_UTF8);
std::string chat_id = _T2A(hook->ptszID, CP_UTF8);
if (isOnline()) {
debugLogA(" > Chat - Outgoing message");
ForkThread(&FacebookProto::SendChatMsgWorker, new send_chat(chat_id, msg));
}
}
break;
case GC_USER_PRIVMESS:
{
facebook_user fbu;
fbu.user_id = _T2A(hook->ptszUID, CP_UTF8);
// Find this contact in list or add new temporary contact
MCONTACT hContact = AddToContactList(&fbu, false, true);
if (!hContact)
break;
CallService(MS_MSG_SENDMESSAGEW, hContact);
}
break;
/*
case GC_USER_LOGMENU:
{
switch(hook->dwData)
{
case 10:
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, invite_to_chat_dialog,
LPARAM(new invite_chat_param(item->id, this)));
break;
case 20:
//chat_leave(id);
break;
}
break;
}
*/
case GC_USER_NICKLISTMENU:
{
MCONTACT hContact = NULL;
if (hook->dwData == 10 || hook->dwData == 20) {
facebook_user fbu;
fbu.user_id = _T2A(hook->ptszUID, CP_UTF8);
// Find this contact in list or add new temporary contact
hContact = AddToContactList(&fbu, false, true);
if (!hContact)
break;
}
switch (hook->dwData) {
case 10:
CallService(MS_USERINFO_SHOWDIALOG, hContact);
break;
case 20:
CallService(MS_HISTORY_SHOWCONTACTHISTORY, hContact);
break;
case 110:
//chat_leave(id);
break;
}
break;
}
case GC_USER_LEAVE:
case GC_SESSION_TERMINATE:
break;
}
return 0;
}
void FacebookProto::AddChatContact(const char *chat_id, const chatroom_participant &user, bool addToLog)
{
// Don't add user if it's already there
if (IsChatContact(chat_id, user.user_id.c_str()))
return;
ptrW tchat_id(mir_a2u(chat_id));
ptrW tnick(mir_a2u_cp(user.nick.c_str(), CP_UTF8));
ptrW tid(mir_a2u(user.user_id.c_str()));
GCEVENT gce = { m_szModuleName, tchat_id, GC_EVENT_JOIN };
gce.dwFlags = addToLog ? GCEF_ADDTOLOG : 0;
gce.ptszNick = tnick;
gce.ptszUID = tid;
gce.time = ::time(nullptr);
gce.bIsMe = (user.role == ROLE_ME);
if (user.is_former) {
gce.ptszStatus = TranslateT("Former");
}
else {
switch (user.role) {
case ROLE_ME:
gce.ptszStatus = TranslateT("Myself");
break;
case ROLE_FRIEND:
gce.ptszStatus = TranslateT("Friend");
break;
case ROLE_NONE:
gce.ptszStatus = TranslateT("User");
break;
}
}
Chat_Event(&gce);
}
void FacebookProto::RemoveChatContact(const char *chat_id, const char *id, const char *name)
{
// We dont want to remove our self-contact from chat. Ever.
if (!mir_strcmp(id, facy.self_.user_id.c_str()))
return;
ptrW tchat_id(mir_a2u(chat_id));
ptrW tnick(mir_a2u_cp(name, CP_UTF8));
ptrW tid(mir_a2u(id));
GCEVENT gce = { m_szModuleName, tchat_id, GC_EVENT_PART };
gce.dwFlags = GCEF_ADDTOLOG;
gce.ptszNick = tnick;
gce.ptszUID = tid;
gce.time = ::time(nullptr);
gce.bIsMe = false;
Chat_Event(&gce);
}
/** Caller must free result */
char *FacebookProto::GetChatUsers(const char *chat_id)
{
ptrW ptszChatID(mir_a2u(chat_id));
GC_INFO gci = { 0 };
gci.Flags = GCF_USERS;
gci.pszModule = m_szModuleName;
gci.pszID = ptszChatID;
Chat_GetInfo(&gci);
// mir_free(gci.pszUsers);
return gci.pszUsers;
}
bool FacebookProto::IsChatContact(const char *chat_id, const char *id)
{
ptrA users(GetChatUsers(chat_id));
return (users != NULL && strstr(users, id) != nullptr);
}
void FacebookProto::AddChat(const char *id, const wchar_t *tname)
{
ptrW tid(mir_a2u(id));
// Create the group chat session
Chat_NewSession(GCW_PRIVMESS, m_szModuleName, tid, tname);
// Send setting events
Chat_AddGroup(m_szModuleName, tid, TranslateT("Myself"));
Chat_AddGroup(m_szModuleName, tid, TranslateT("Friend"));
Chat_AddGroup(m_szModuleName, tid, TranslateT("User"));
Chat_AddGroup(m_szModuleName, tid, TranslateT("Former"));
// Finish initialization
bool hideChats = getBool(FACEBOOK_KEY_HIDE_CHATS, DEFAULT_HIDE_CHATS);
Chat_Control(m_szModuleName, tid, (hideChats ? WINDOW_HIDDEN : SESSION_INITDONE));
Chat_Control(m_szModuleName, tid, SESSION_ONLINE);
}
INT_PTR FacebookProto::OnJoinChat(WPARAM hContact, LPARAM)
{
if (isOffline() || facy.dtsg_.empty() || !m_enableChat || IsSpecialChatRoom(hContact))
return 0;
ptrW idT(getWStringA(hContact, "ChatRoomID"));
ptrA threadId(getStringA(hContact, FACEBOOK_KEY_TID));
if (!idT || !threadId)
return 0;
facebook_chatroom *fbc;
std::string thread_id = threadId;
auto it = facy.chat_rooms.find(thread_id);
if (it != facy.chat_rooms.end())
fbc = it->second;
else {
// We don't have this chat loaded in memory yet, lets load some info (name, list of users)
fbc = new facebook_chatroom(thread_id);
LoadChatInfo(fbc);
facy.chat_rooms.insert(std::make_pair(thread_id, fbc));
// Update loaded info about this chat
setByte(hContact, FACEBOOK_KEY_CHAT_CAN_REPLY, fbc->can_reply);
setByte(hContact, FACEBOOK_KEY_CHAT_READ_ONLY, fbc->read_only);
setByte(hContact, FACEBOOK_KEY_CHAT_IS_ARCHIVED, fbc->is_archived);
setByte(hContact, FACEBOOK_KEY_CHAT_IS_SUBSCRIBED, fbc->is_subscribed);
}
// RM TODO: better use check if chatroom exists/is in db/is online... no?
// like: if (ChatIDToHContact(thread_id) == NULL) {
ptrA users(GetChatUsers(thread_id.c_str()));
if (users == NULL) {
// Add chatroom
AddChat(fbc->thread_id.c_str(), fbc->chat_name.c_str());
// Add chat contacts
for (std::map::iterator jt = fbc->participants.begin(); jt != fbc->participants.end(); ++jt) {
AddChatContact(fbc->thread_id.c_str(), jt->second, false);
}
// Load last messages
delSetting(hContact, FACEBOOK_KEY_MESSAGE_ID); // We're creating new chatroom so we want load all recent messages
ForkThread(&FacebookProto::LoadLastMessages, new MCONTACT(hContact));
}
return 0;
}
INT_PTR FacebookProto::OnLeaveChat(WPARAM wParam, LPARAM)
{
ptrW idT(wParam ? getWStringA(wParam, "ChatRoomID") : nullptr);
Chat_Control(m_szModuleName, idT, SESSION_OFFLINE);
Chat_Terminate(m_szModuleName, idT);
if (!wParam)
facy.clear_chatrooms();
else if (!IsSpecialChatRoom(wParam)) {
ptrA threadId(getStringA(wParam, FACEBOOK_KEY_TID));
if (!threadId)
return 0;
auto it = facy.chat_rooms.find(std::string(threadId));
if (it != facy.chat_rooms.end()) {
delete it->second;
facy.chat_rooms.erase(it);
}
}
return 0;
}
int FacebookProto::OnGCMenuHook(WPARAM, LPARAM lParam)
{
GCMENUITEMS *gcmi = (GCMENUITEMS*)lParam;
if (gcmi == nullptr || _stricmp(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 }
};
Chat_AddMenuItems(gcmi->hMenu, _countof(Items), Items);
}
else if (gcmi->Type == MENU_ON_NICKLIST) {
if (!_stricmp(facy.self_.user_id.c_str(), _T2A(gcmi->pszUID))) {
/*static const struct gc_item Items[] =
{
{ LPGENW("User &details"), 10, MENU_ITEM, FALSE },
{ LPGENW("User &history"), 20, MENU_ITEM, FALSE },
{ L"", 100, MENU_SEPARATOR, FALSE },
{ LPGENW("&Leave chat session"), 110, MENU_ITEM, FALSE }
};
gcmi->nItems = _countof(Items);
gcmi->Item = (gc_item*)Items;*/
}
else {
static const struct gc_item Items[] =
{
{ LPGENW("User &details"), 10, MENU_ITEM, FALSE },
{ LPGENW("User &history"), 20, MENU_ITEM, FALSE }
};
Chat_AddMenuItems(gcmi->hMenu, _countof(Items), Items);
}
}
return 0;
}
bool FacebookProto::IsSpecialChatRoom(MCONTACT hContact)
{
if (!isChatRoom(hContact))
return false;
ptrA id(getStringA(hContact, "ChatRoomID"));
return id && !mir_strcmp(id, FACEBOOK_NOTIFICATIONS_CHATROOM);
}
void FacebookProto::PrepareNotificationsChatRoom()
{
if (!getBool(FACEBOOK_KEY_NOTIFICATIONS_CHATROOM, DEFAULT_NOTIFICATIONS_CHATROOM))
return;
// Prepare notifications chatroom if not exists
MCONTACT hNotificationsChatRoom = ChatIDToHContact(FACEBOOK_NOTIFICATIONS_CHATROOM);
if (hNotificationsChatRoom == NULL || getDword(hNotificationsChatRoom, "Status", ID_STATUS_OFFLINE) != ID_STATUS_ONLINE) {
wchar_t nameT[200];
mir_snwprintf(nameT, L"%s: %s", m_tszUserName, TranslateT("Notifications"));
// Create the group chat session
Chat_NewSession(GCW_PRIVMESS, m_szModuleName, _A2W(FACEBOOK_NOTIFICATIONS_CHATROOM), nameT);
// Send setting events
Chat_Control(m_szModuleName, _A2W(FACEBOOK_NOTIFICATIONS_CHATROOM), WINDOW_HIDDEN);
Chat_Control(m_szModuleName, _A2W(FACEBOOK_NOTIFICATIONS_CHATROOM), SESSION_ONLINE);
}
}
void FacebookProto::UpdateNotificationsChatRoom(facebook_notification *notification)
{
if (!getBool(FACEBOOK_KEY_NOTIFICATIONS_CHATROOM, DEFAULT_NOTIFICATIONS_CHATROOM))
return;
std::stringstream text;
text << notification->text << "\n\n" << PrepareUrl(notification->link);
std::string message = text.str();
utils::text::replace_all(&message, "%", "%%");
ptrW idT(mir_wstrdup(_A2W(FACEBOOK_NOTIFICATIONS_CHATROOM)));
ptrW messageT(mir_a2u_cp(message.c_str(), CP_UTF8));
GCEVENT gce = { m_szModuleName, _A2W(FACEBOOK_NOTIFICATIONS_CHATROOM), GC_EVENT_MESSAGE };
gce.ptszText = messageT;
gce.time = notification->time ? notification->time : ::time(nullptr);
gce.bIsMe = false;
gce.dwFlags |= GCEF_ADDTOLOG;
gce.ptszNick = TranslateT("Notifications");
gce.ptszUID = idT;
Chat_Event(&gce);
}
std::string FacebookProto::GenerateChatName(facebook_chatroom *fbc)
{
std::string name = "";
unsigned int namesUsed = 0;
for (auto it = fbc->participants.begin(); it != fbc->participants.end(); ++it) {
std::string participant = it->second.nick;
// Ignore self contact, empty and numeric only participant names
if (it->second.role == ROLE_ME || participant.empty() || participant.find_first_not_of("0123456789") == std::string::npos)
continue;
if (namesUsed > 0)
name += ", ";
name += utils::text::prepare_name(participant, false);
if (++namesUsed >= FACEBOOK_CHATROOM_NAMES_COUNT)
break;
}
// Participants.size()-1 because we ignore self contact
if (fbc->participants.size() - 1 > namesUsed) {
wchar_t more[200];
mir_snwprintf(more, TranslateT("%s and more (%d)"), fbc->chat_name.c_str(), fbc->participants.size() - 1 - namesUsed); // -1 because we ignore self contact
fbc->chat_name = more;
}
// If there are no participants to create a name from, use just thread_id
if (name.empty())
name = fbc->thread_id.c_str();
return name;
}