From 7538e4d50ab714f503df3c82c9ef94e90fb49ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20P=C3=B6sel?= Date: Sun, 29 Sep 2013 20:47:11 +0000 Subject: Facebook: first experimental implementation of multi user chats (thanks to nobodyreal for patch) git-svn-id: http://svn.miranda-ng.org/main/trunk@6273 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/FacebookRM/src/chat.cpp | 127 ++++++++++++++++++++++++++++++++-- protocols/FacebookRM/src/client.h | 3 +- protocols/FacebookRM/src/entities.h | 12 ++++ protocols/FacebookRM/src/json.cpp | 127 +++++++++++++++++++++++++++++----- protocols/FacebookRM/src/messages.cpp | 14 ++-- protocols/FacebookRM/src/proto.cpp | 3 +- protocols/FacebookRM/src/proto.h | 3 +- 7 files changed, 259 insertions(+), 30 deletions(-) (limited to 'protocols/FacebookRM/src') diff --git a/protocols/FacebookRM/src/chat.cpp b/protocols/FacebookRM/src/chat.cpp index cbb52248ac..e0414ae9c7 100644 --- a/protocols/FacebookRM/src/chat.cpp +++ b/protocols/FacebookRM/src/chat.cpp @@ -21,6 +21,8 @@ along with this program. If not, see . */ #include "common.h" +#include +#include void FacebookProto::UpdateChat(const char *chat_id, const char *id, const char *name, const char *message, DWORD timestamp) { @@ -45,9 +47,15 @@ void FacebookProto::UpdateChat(const char *chat_id, const char *id, const char * gce.ptszUID = tid; CallServiceSync(MS_GC_EVENT,0,reinterpret_cast(&gce)); + + std::map::iterator chatroom = facy.chat_rooms.find(chat_id); + chatroom->second.message_readers = ""; + + HANDLE hChatContact = ChatIDToHContact(chat_id); + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)mir_a2u("Unseen")); } -int FacebookProto::OnChatOutgoing(WPARAM wParam,LPARAM lParam) +int FacebookProto::OnGCEvent(WPARAM wParam,LPARAM lParam) { GCHOOK *hook = reinterpret_cast(lParam); @@ -69,11 +77,61 @@ int FacebookProto::OnChatOutgoing(WPARAM wParam,LPARAM lParam) break; } - case GC_USER_LEAVE: - case GC_SESSION_TERMINATE: + case GC_USER_PRIVMESS: + { + char* sn = mir_t2a(hook->ptszUID); + HANDLE hContact = ContactIDToHContact(sn); + mir_free(sn); + CallService(MS_MSG_SENDMESSAGET, (WPARAM)hContact, 0); + + 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: { + char *sn = mir_t2a(hook->ptszUID); + HANDLE hContact = ContactIDToHContact(sn); + mir_free(sn); + + switch (hook->dwData) + { + case 10: + CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)hContact, 0); + break; + + case 20: + CallService(MS_HISTORY_SHOWCONTACTHISTORY, (WPARAM)hContact, 0); + break; + + case 110: + //chat_leave(id); + break; + } + break; } + + case GC_USER_LEAVE: + case GC_SESSION_TERMINATE: + break; } return 0; @@ -102,6 +160,10 @@ void FacebookProto::AddChatContact(const char *chat_id, const char *id, const ch else gce.ptszStatus = _T("Normal"); + std::map::iterator room = facy.chat_rooms.find(chat_id); + if(room != facy.chat_rooms.end()) + room->second.participants.insert(std::make_pair(id, name)); + CallServiceSync(MS_GC_EVENT,0,reinterpret_cast(&gce)); } @@ -128,6 +190,10 @@ void FacebookProto::RemoveChatContact(const char *chat_id, const char *id) gce.time = ::time(NULL); gce.bIsMe = false;//!strcmp(id, facy.self_.user_id.c_str()); + std::map::iterator room = facy.chat_rooms.find(chat_id); + if (room != facy.chat_rooms.end()) + room->second.participants.erase(id); + CallServiceSync(MS_GC_EVENT,0,reinterpret_cast(&gce)); } @@ -187,6 +253,10 @@ void FacebookProto::AddChat(const char *id, const char *name) gcd.iType = GC_EVENT_CONTROL; gce.time = ::time(NULL); gce.pDest = &gcd; + + facebook_chatroom chatroom; + chatroom.chat_name = name; + facy.chat_rooms.insert(std::make_pair(id, chatroom)); // Add self contact AddChatContact(id, facy.self_.user_id.c_str(), facy.self_.real_name.c_str()); @@ -293,4 +363,53 @@ void FacebookProto::SetChatStatus(int status) CallServiceSync(MS_GC_EVENT,SESSION_OFFLINE,reinterpret_cast(&gce)); } } -*/ \ No newline at end of file +*/ + +int FacebookProto::OnGCMenuHook(WPARAM, LPARAM lParam) +{ + GCMENUITEMS *gcmi= (GCMENUITEMS*) lParam; + + if (gcmi == NULL || _stricmp(gcmi->pszModule, m_szModuleName)) return 0; + + if (gcmi->Type == MENU_ON_LOG) + { + static const struct gc_item Items[] = + { + { LPGENT("&Invite user..."), 10, MENU_ITEM, FALSE }, + { LPGENT("&Leave chat session"), 20, MENU_ITEM, FALSE } + }; + gcmi->nItems = SIZEOF(Items); + gcmi->Item = (gc_item*)Items; + } + else if (gcmi->Type == MENU_ON_NICKLIST) + { + char* email = mir_t2a(gcmi->pszUID); + if (!_stricmp(facy.self_.user_id.c_str(), email)) + { + /*static const struct gc_item Items[] = + { + { LPGENT("User &details"), 10, MENU_ITEM, FALSE }, + { LPGENT("User &history"), 20, MENU_ITEM, FALSE }, + { _T(""), 100, MENU_SEPARATOR, FALSE }, + { LPGENT("&Leave chat session"), 110, MENU_ITEM, FALSE } + }; + gcmi->nItems = SIZEOF(Items); + gcmi->Item = (gc_item*)Items;*/ + gcmi->nItems = 0; + gcmi->Item = NULL; + } + else + { + static const struct gc_item Items[] = + { + { LPGENT("User &details"), 10, MENU_ITEM, FALSE }, + { LPGENT("User &history"), 20, MENU_ITEM, FALSE } + }; + gcmi->nItems = SIZEOF(Items); + gcmi->Item = (gc_item*)Items; + } + mir_free(email); + } + + return 0; +} diff --git a/protocols/FacebookRM/src/client.h b/protocols/FacebookRM/src/client.h index a14a29b483..41a8b6a1d0 100644 --- a/protocols/FacebookRM/src/client.h +++ b/protocols/FacebookRM/src/client.h @@ -95,6 +95,7 @@ public: std::map cookies; std::map pages; + std::map chat_rooms; std::string get_newsfeed_type(); std::string get_server_type(); @@ -102,7 +103,7 @@ public: char* load_cookies(); void store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headers_count); - void clear_cookies(); + void clear_cookies(); //////////////////////////////////////////////////////////// diff --git a/protocols/FacebookRM/src/entities.h b/protocols/FacebookRM/src/entities.h index 20008b2f21..4ad4612985 100644 --- a/protocols/FacebookRM/src/entities.h +++ b/protocols/FacebookRM/src/entities.h @@ -59,6 +59,18 @@ struct facebook_user } }; +struct facebook_chatroom +{ + HANDLE handle; + + std::string chat_name; + std::map participants; + + std::string message_readers; + + DWORD last_active; +}; + struct facebook_message { std::string user_id; diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp index be1e6232ab..529dd8abd1 100644 --- a/protocols/FacebookRM/src/json.cpp +++ b/protocols/FacebookRM/src/json.cpp @@ -418,14 +418,65 @@ int facebook_json_parser::parse_messages(void* data, std::vector< facebook_messa if (reader == NULL || time == NULL) continue; - // TODO: add check for chat contacts - HANDLE hContact = proto->ContactIDToHContact(json_as_pstring(reader)); - if (hContact) { - TCHAR ttime[64], tstr[100]; - _tcsftime(ttime, SIZEOF(ttime), _T("%X"), utils::conversion::fbtime_to_timeinfo(json_as_float(time))); - mir_sntprintf(tstr, SIZEOF(tstr), TranslateT("Message read: %s"), ttime); - - CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hContact, (LPARAM)tstr); + JSONNODE *threadid = json_get(it, "tid"); + if (threadid != NULL) { // multi user chat + const char *tid = json_as_string(threadid); + const char *reader_id = json_as_string(reader); + + std::map::iterator chatroom = proto->facy.chat_rooms.find(tid); + if (chatroom != proto->facy.chat_rooms.end()) { + std::map::const_iterator participant = chatroom->second.participants.find(reader_id); + if (participant == chatroom->second.participants.end()) { + std::string search = utils::url::encode(tid) + "?"; + http::response resp = proto->facy.flap(REQUEST_USER_INFO, NULL, &search); + + if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end()) { + search = utils::text::source_get_value(&resp.headers["Location"], 2, FACEBOOK_SERVER_MOBILE"/", "_rdr", true); + resp = proto->facy.flap(REQUEST_USER_INFO, NULL, &search); + } + + proto->facy.validate_response(&resp); + + if (resp.code == HTTP_CODE_OK) { + std::string about = utils::text::source_get_value(&resp.data, 2, "
", "AddChatContact(tid, reader_id, name.c_str()); + } + } + + participant = chatroom->second.participants.find(reader_id); + if (participant != chatroom->second.participants.end()) { + HANDLE hChatContact = proto->ChatIDToHContact(tid); + const char *participant_name = participant->second.c_str(); + + if (!chatroom->second.message_readers.empty()) + chatroom->second.message_readers += ", "; + chatroom->second.message_readers += participant_name; + + TCHAR ttime[64], tstr[100]; + _tcsftime(ttime, SIZEOF(ttime), _T("%X"), utils::conversion::fbtime_to_timeinfo(json_as_float(time))); + mir_sntprintf(tstr, SIZEOF(tstr), TranslateT("Message read: %s by %s"), ttime, ptrT(mir_utf8decodeT(chatroom->second.message_readers.c_str()))); + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hChatContact, (LPARAM)tstr); + } + } + } else { // classic contact + HANDLE hContact = proto->ContactIDToHContact(json_as_pstring(reader)); + if (hContact) { + TCHAR ttime[64], tstr[100]; + _tcsftime(ttime, SIZEOF(ttime), _T("%X"), utils::conversion::fbtime_to_timeinfo(json_as_float(time))); + mir_sntprintf(tstr, SIZEOF(tstr), TranslateT("Message read: %s"), ttime); + + CallService(MS_MSG_SETSTATUSTEXT, (WPARAM)hContact, (LPARAM)tstr); + } } } else if (t == "deliver") { // inbox message (multiuser or direct) @@ -461,14 +512,58 @@ int facebook_json_parser::parse_messages(void* data, std::vector< facebook_messa if (message_text.empty()) continue; - facebook_message* message = new facebook_message(); - message->message_text = message_text; - message->sender_name = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(json_as_pstring(sender_name))); - message->time = utils::time::fix_timestamp(json_as_float(timestamp)); - message->user_id = id; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? - message->message_id = message_id; - - messages->push_back(message); + // Multi-user + JSONNODE *gthreadinfo = json_get(msg, "group_thread_info"); + if (gthreadinfo != NULL) { + JSONNODE *thread_name = json_get(gthreadinfo, "name"); + const char* thread_id = json_as_string(tid); + + // RM TODO: better use check if chatroom exists/is in db/is online... no? + /// e.g. HANDLE hChatContact = proto->ChatIDToHContact(thread_id); ? + if (proto->GetChatUsers(thread_id) == NULL) { + // Set thread id (TID) for later. + proto->AddChat(thread_id, json_as_string(thread_name)); + db_set_s(proto->ChatIDToHContact(thread_id), proto->m_szModuleName, FACEBOOK_KEY_TID, thread_id); + } + + // This is a recent 5 person listing of participants. + JSONNODE *participants_ids = json_get(gthreadinfo, "participant_ids"); + JSONNODE *participants_names = json_get(gthreadinfo, "participant_names"); + for (unsigned int n = 0; n < json_size(participants_ids); n++) { + JSONNODE *idItr = json_at(participants_ids, n); + const char* pId = json_as_string(idItr); + + JSONNODE *nameItr = json_at(participants_names, n); + const char* pName = json_as_string(nameItr); + + if (!proto->IsChatContact(thread_id, pId)) { + proto->AddChatContact(thread_id, pId, pName); + } + } + + std::string senderName = json_as_string(sender_name); + std::string::size_type pos; + /*if ((pos = senderName.find(" ")) != std::string::npos) { + senderName = senderName.substr(0, pos); + }*/ + + // Last fall-back for adding this sender (Incase was not in the participants) - Is this even needed? + if (!proto->IsChatContact(thread_id, id.c_str())) { + proto->AddChatContact(thread_id, id.c_str(), senderName.c_str()); + } + + // Update chat with message. + proto->UpdateChat(thread_id, id.c_str(), senderName.c_str(), message_text.c_str(), utils::time::fix_timestamp(json_as_float(timestamp))); + } else { + facebook_message* message = new facebook_message(); + message->message_text = message_text; + message->sender_name = utils::text::special_expressions_decode(utils::text::slashu_to_utf8(json_as_string(sender_name))); + message->time = utils::time::fix_timestamp(json_as_float(timestamp)); + message->user_id = id; // TODO: Check if we have contact with this ID in friendlist and otherwise do something different? + message->message_id = message_id; + + messages->push_back(message); + } } } else if (t == "thread_msg") { // multiuser message diff --git a/protocols/FacebookRM/src/messages.cpp b/protocols/FacebookRM/src/messages.cpp index 298413cec4..5a7dc086ae 100644 --- a/protocols/FacebookRM/src/messages.cpp +++ b/protocols/FacebookRM/src/messages.cpp @@ -97,8 +97,10 @@ void FacebookProto::SendChatMsgWorker(void *p) Log(" Got thread info: %s = %s", data->chat_id.c_str(), tid.c_str()); } - if (!tid.empty()) - facy.send_message(tid, data->msg, &err_message, MESSAGE_TID); + if (!tid.empty()) { + if (facy.send_message(tid, data->msg, &err_message, MESSAGE_TID)) + UpdateChat(data->chat_id.c_str(), facy.self_.user_id.c_str(), facy.self_.real_name.c_str(), data->msg.c_str()); + } } delete data; @@ -110,7 +112,7 @@ int FacebookProto::SendMsg(HANDLE hContact, int flags, const char *msg) if (flags & PREF_UNICODE) msg = mir_utf8encode(msg); - facy.msgid_ = (facy.msgid_ % 1024)+1; + facy.msgid_ = (facy.msgid_ % 1024) + 1; ForkThread(&FacebookProto::SendMsgWorker, new send_direct(hContact, msg, (HANDLE)facy.msgid_)); return facy.msgid_; } @@ -138,15 +140,13 @@ void FacebookProto::SendTypingWorker(void *p) facy.is_typing_ = (typing->status == PROTOTYPE_SELFTYPING_ON); SleepEx(2000, true); - if (!facy.is_typing_ == (typing->status == PROTOTYPE_SELFTYPING_ON)) - { + if (!facy.is_typing_ == (typing->status == PROTOTYPE_SELFTYPING_ON)) { delete typing; return; } DBVARIANT dbv; - if (!getString(typing->hContact, FACEBOOK_KEY_ID, &dbv)) - { + if (!getString(typing->hContact, FACEBOOK_KEY_ID, &dbv)) { std::string data = "&source=mercury-chat"; data += (typing->status == PROTOTYPE_SELFTYPING_ON ? "&typ=1" : "&typ=0"); // PROTOTYPE_SELFTYPING_OFF data += "&to=" + std::string(dbv.pszVal); diff --git a/protocols/FacebookRM/src/proto.cpp b/protocols/FacebookRM/src/proto.cpp index b07a209a2d..4c122c8f99 100644 --- a/protocols/FacebookRM/src/proto.cpp +++ b/protocols/FacebookRM/src/proto.cpp @@ -48,9 +48,10 @@ FacebookProto::FacebookProto(const char* proto_name,const TCHAR* username) : HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &FacebookProto::OnBuildStatusMenu); HookProtoEvent(ME_OPT_INITIALISE, &FacebookProto::OnOptionsInit); - HookProtoEvent(ME_GC_EVENT, &FacebookProto::OnChatOutgoing); HookProtoEvent(ME_IDLE_CHANGED, &FacebookProto::OnIdleChanged); HookProtoEvent(ME_TTB_MODULELOADED, &FacebookProto::OnToolbarInit); + HookProtoEvent(ME_GC_EVENT, &FacebookProto::OnGCEvent); + HookProtoEvent(ME_GC_BUILDMENU, &FacebookProto::OnGCMenuHook); InitHotkeys(); InitPopups(); diff --git a/protocols/FacebookRM/src/proto.h b/protocols/FacebookRM/src/proto.h index 65aa316ee5..d3cc040fb2 100644 --- a/protocols/FacebookRM/src/proto.h +++ b/protocols/FacebookRM/src/proto.h @@ -146,7 +146,8 @@ public: int __cdecl OnPreShutdown(WPARAM,LPARAM); int __cdecl OnPrebuildContactMenu(WPARAM,LPARAM); int __cdecl OnIdleChanged(WPARAM,LPARAM); - int __cdecl OnChatOutgoing(WPARAM,LPARAM); + int __cdecl OnGCEvent(WPARAM,LPARAM); + int __cdecl OnGCMenuHook(WPARAM,LPARAM); // Loops bool NegotiateConnection(); -- cgit v1.2.3