From 845d6d07ee1c6c87c7dacae4f0ba0a803a5aff3d Mon Sep 17 00:00:00 2001 From: George Hazan Date: Tue, 18 Feb 2020 23:22:20 +0300 Subject: Twitter: - partially implements #2218 (support for reading direct messages and marking them as read); - user id parameter added to database; - support added for server message ids; - code cleaning; --- protocols/Twitter/src/connection.cpp | 39 ++++++++----- protocols/Twitter/src/contacts.cpp | 34 +++++++++++ protocols/Twitter/src/proto.cpp | 12 +++- protocols/Twitter/src/proto.h | 108 ++++++++++++++++++++--------------- protocols/Twitter/src/stdafx.h | 1 + protocols/Twitter/src/twitter.cpp | 11 ++++ protocols/Twitter/src/utility.cpp | 35 ++++++------ 7 files changed, 161 insertions(+), 79 deletions(-) diff --git a/protocols/Twitter/src/connection.cpp b/protocols/Twitter/src/connection.cpp index 278afcb4ae..b927de5c4d 100644 --- a/protocols/Twitter/src/connection.cpp +++ b/protocols/Twitter/src/connection.cpp @@ -373,11 +373,13 @@ void CTwitterProto::UpdateFriends() if (m_szUserName == username.c_str()) continue; + std::string id = one["id_str"].as_string(); std::string real_name = one["name"].as_string(); std::string profile_image_url = one["profile_image_url"].as_string(); std::string status_text = one["status"]["text"].as_string(); MCONTACT hContact = AddToClientList(username.c_str(), status_text.c_str()); + setString(hContact, TWITTER_KEY_ID, id.c_str()); setUString(hContact, "Nick", real_name.c_str()); UpdateAvatar(hContact, profile_image_url.c_str()); } @@ -539,13 +541,10 @@ void CTwitterProto::UpdateStatuses(bool pre_read, bool popups, bool tweetToMsg) db_pod_set(0, m_szModuleName, TWITTER_KEY_SINCEID, since_id_); disconnectionCount = 0; debugLogA("***** Status messages updated"); - } void CTwitterProto::UpdateMessages(bool pre_read) { - OBJLIST messages(50); - auto *req = new AsyncHttpRequest(REQUEST_GET, m_szBaseUrl + "1.1/direct_messages/events/list.json"); req << INT_PARAM("count", 50); if (dm_since_id_ != 0) @@ -564,25 +563,35 @@ void CTwitterProto::UpdateMessages(bool pre_read) } for (auto &one : root["events"]) { - twitter_user *u = new twitter_user(); - u->username = one["sender_screen_name"].as_string(); - u->status.text = one["text"].as_string(); - u->status.time = parse_time(one["created_at"].as_string().c_str()); - messages.insert(u); + std::string type = one["type"].as_string(); + if (type != "message_create") + continue; - twitter_id id = _atoi64(one["id"].as_string().c_str()); + auto &msgCreate = one["message_create"]; + std::string sender = msgCreate["sender_id"].as_string(); + MCONTACT hContact = FindContactById(sender.c_str()); + if (hContact == INVALID_CONTACT_ID) { + debugLogA("Message from unknown sender %s, ignored", sender.c_str()); + continue; + } + + std::string text = msgCreate["message_data"]["text"].as_string(); + __time64_t time = _atoi64(one["created_timestamp"].as_string().c_str()) / 1000; + + std::string msgid = one["id"].as_string(); + if (db_event_getById(m_szModuleName, msgid.c_str())) + continue; + + twitter_id id = _atoi64(msgid.c_str()); if (id > dm_since_id_) dm_since_id_ = id; - } - - for (auto &it : messages.rev_iter()) { - MCONTACT hContact = AddToClientList(it->username.c_str(), ""); PROTORECVEVENT recv = { 0 }; if (pre_read) recv.flags |= PREF_CREATEREAD; - recv.szMessage = const_cast(it->status.text.c_str()); - recv.timestamp = static_cast(it->status.time); + recv.szMessage = const_cast(text.c_str()); + recv.timestamp = static_cast(time); + recv.szMsgId = msgid.c_str(); ProtoChainRecvMsg(hContact, &recv); } diff --git a/protocols/Twitter/src/contacts.cpp b/protocols/Twitter/src/contacts.cpp index 7cdf32b6e3..01524b6a12 100644 --- a/protocols/Twitter/src/contacts.cpp +++ b/protocols/Twitter/src/contacts.cpp @@ -175,6 +175,26 @@ int CTwitterProto::OnContactDeleted(WPARAM wParam, LPARAM) return 0; } +int CTwitterProto::OnMarkedRead(WPARAM, LPARAM hDbEvent) +{ + MCONTACT hContact = db_event_getContact(hDbEvent); + if (!hContact) + return 0; + + // filter out only events of my protocol + const char *szProto = Proto_GetBaseAccountName(hContact); + if (mir_strcmp(szProto, m_szModuleName)) + return 0; + + auto *pMark = (m_arChatMarks.find((CChatMark *)&hDbEvent)); + if (pMark) { + mark_read(hContact, pMark->szId); + m_arChatMarks.remove(pMark); + } + + return 0; +} + // ************************* bool CTwitterProto::IsMyContact(MCONTACT hContact, bool include_chat) @@ -201,6 +221,20 @@ MCONTACT CTwitterProto::UsernameToHContact(const char *name) return 0; } +MCONTACT CTwitterProto::FindContactById(const char *id) +{ + for (auto &hContact : AccContacts()) { + if (getByte(hContact, "ChatRoom")) + continue; + + if (getMStringA(hContact, TWITTER_KEY_ID) == id) + return hContact; + } + + return INVALID_CONTACT_ID; +} + + MCONTACT CTwitterProto::AddToClientList(const char *name, const char *status) { // First, check if this contact exists diff --git a/protocols/Twitter/src/proto.cpp b/protocols/Twitter/src/proto.cpp index 1a6996192c..01da0ec206 100644 --- a/protocols/Twitter/src/proto.cpp +++ b/protocols/Twitter/src/proto.cpp @@ -26,7 +26,8 @@ static volatile LONG g_msgid = 1; CTwitterProto::CTwitterProto(const char *proto_name, const wchar_t *username) : PROTO(proto_name, username), m_szChatId(mir_utf8encodeW(username)), - m_szBaseUrl("https://api.twitter.com/") + m_szBaseUrl("https://api.twitter.com/"), + m_arChatMarks(10, NumericKeySortT) { CreateProtoService(PS_CREATEACCMGRUI, &CTwitterProto::SvcCreateAccMgrUI); @@ -38,6 +39,7 @@ CTwitterProto::CTwitterProto(const char *proto_name, const wchar_t *username) : HookProtoEvent(ME_OPT_INITIALISE, &CTwitterProto::OnOptionsInit); HookProtoEvent(ME_DB_CONTACT_DELETED, &CTwitterProto::OnContactDeleted); + HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CTwitterProto::OnMarkedRead); HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &CTwitterProto::OnBuildStatusMenu); // Initialize hotkeys @@ -148,6 +150,14 @@ void CTwitterProto::SendSuccess(void *p) delete data; } +MEVENT CTwitterProto::RecvMsg(MCONTACT hContact, PROTORECVEVENT *pre) +{ + MEVENT res = CSuper::RecvMsg(hContact, pre); + if (pre->szMsgId) + m_arChatMarks.insert(new CChatMark(res, pre->szMsgId)); + + return res; +} int CTwitterProto::SendMsg(MCONTACT hContact, int, const char *msg) { if (m_iStatus != ID_STATUS_ONLINE) diff --git a/protocols/Twitter/src/proto.h b/protocols/Twitter/src/proto.h index 147fee84c8..6067841973 100644 --- a/protocols/Twitter/src/proto.h +++ b/protocols/Twitter/src/proto.h @@ -44,6 +44,18 @@ struct AsyncHttpRequest : public MHttpRequest AsyncHttpRequest(int type, const char *szUrl); }; +struct CChatMark +{ + CChatMark(MEVENT _p1, const CMStringA &_p2) : + hEvent(_p1), + szId(_p2) + { + } + + MEVENT hEvent; + CMStringA szId; +}; + class CTwitterProto : public PROTO { ptrA m_szChatId; @@ -51,14 +63,14 @@ class CTwitterProto : public PROTO http::response request_token(); http::response request_access_tokens(); - void set_base_url(const CMStringA &base_url); - bool get_info(const CMStringA &name, twitter_user *); bool get_info_by_email(const CMStringA &email, twitter_user *); bool add_friend(const CMStringA &name, twitter_user &u); void remove_friend(const CMStringA &name); + void mark_read(MCONTACT hContact, const CMStringA &msgId); + void set_status(const CMStringA &text); void send_direct(const CMStringA &name, const CMStringA &text); @@ -73,6 +85,24 @@ class CTwitterProto : public PROTO CMStringA m_szAccessTokenSecret; CMStringA m_szPin; + CMStringW GetAvatarFolder(); + + mir_cs signon_lock_; + mir_cs avatar_lock_; + mir_cs twitter_lock_; + + OBJLIST m_arChatMarks; + + HNETLIBUSER hAvatarNetlib_; + HANDLE hMsgLoop_; + + twitter_id since_id_; + twitter_id dm_since_id_; + + bool in_chat_; + + int disconnectionCount; + // OAuthWebRequest used for all OAuth related queries // // consumerKey and consumerSecret - must be provided for every call, they identify the application @@ -91,6 +121,32 @@ class CTwitterProto : public PROTO HNETLIBCONN m_hConnHttp; void Disconnect(void) { if (m_hConnHttp) Netlib_CloseHandle(m_hConnHttp); m_hConnHttp = nullptr; } + bool NegotiateConnection(); + + void UpdateStatuses(bool pre_read, bool popups, bool tweetToMsg); + void UpdateMessages(bool pre_read); + void UpdateFriends(); + void UpdateAvatar(MCONTACT, const CMStringA &, bool force = false); + + int ShowPinDialog(); + void ShowPopup(const wchar_t *, int Error = 0); + void ShowPopup(const char *, int Error = 0); + void ShowContactPopup(MCONTACT, const CMStringA &, const CMStringA *); + + bool IsMyContact(MCONTACT, bool include_chat = false); + MCONTACT UsernameToHContact(const char *); + MCONTACT AddToClientList(const char *, const char *); + MCONTACT FindContactById(const char *); + + static void CALLBACK APC_callback(ULONG_PTR p); + + void UpdateChat(const twitter_user &update); + void AddChatContact(const char *name, const char *nick = nullptr); + void DeleteChatContact(const char *name); + void SetChatStatus(int); + + void resetOAuthKeys(); + public: CTwitterProto(const char*,const wchar_t*); ~CTwitterProto(); @@ -106,6 +162,7 @@ public: HANDLE SearchBasic(const wchar_t *) override; HANDLE SearchByEmail(const wchar_t *) override; + MEVENT RecvMsg(MCONTACT hContact, PROTORECVEVENT *) override; int SendMsg(MCONTACT, int, const char *) override; int SetStatus(int) override; @@ -133,18 +190,18 @@ public: ////////////////////////////////////////////////////////////////////////////////////// // Events + int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); + int __cdecl OnChatOutgoing(WPARAM, LPARAM); int __cdecl OnContactDeleted(WPARAM,LPARAM); - int __cdecl OnBuildStatusMenu(WPARAM,LPARAM); + int __cdecl OnMarkedRead(WPARAM, LPARAM); int __cdecl OnOptionsInit(WPARAM,LPARAM); int __cdecl OnPrebuildContactMenu(WPARAM,LPARAM); - int __cdecl OnChatOutgoing(WPARAM,LPARAM); void __cdecl SendTweetWorker(void *); ////////////////////////////////////////////////////////////////////////////////////// // Threads -private: void __cdecl AddToListWorker(void *p); void __cdecl SendSuccess(void *); void __cdecl DoSearch(void *); @@ -153,47 +210,6 @@ private: void __cdecl GetAwayMsgWorker(void *); void __cdecl UpdateAvatarWorker(void *); void __cdecl UpdateInfoWorker(void *); - - bool NegotiateConnection(); - - void UpdateStatuses(bool pre_read,bool popups, bool tweetToMsg); - void UpdateMessages(bool pre_read); - void UpdateFriends(); - void UpdateAvatar(MCONTACT, const CMStringA &, bool force = false); - - int ShowPinDialog(); - void ShowPopup(const wchar_t *, int Error = 0); - void ShowPopup(const char *, int Error = 0); - void ShowContactPopup(MCONTACT, const CMStringA &, const CMStringA *); - - bool IsMyContact(MCONTACT, bool include_chat = false); - MCONTACT UsernameToHContact(const char *); - MCONTACT AddToClientList(const char *, const char *); - - static void CALLBACK APC_callback(ULONG_PTR p); - - void UpdateChat(const twitter_user &update); - void AddChatContact(const char *name,const char *nick = nullptr); - void DeleteChatContact(const char *name); - void SetChatStatus(int); - - void CTwitterProto::resetOAuthKeys(); - - CMStringW GetAvatarFolder(); - - mir_cs signon_lock_; - mir_cs avatar_lock_; - mir_cs twitter_lock_; - - HNETLIBUSER hAvatarNetlib_; - HANDLE hMsgLoop_; - - twitter_id since_id_; - twitter_id dm_since_id_; - - bool in_chat_; - - int disconnectionCount; }; struct CMPlugin : public ACCPROTOPLUGIN diff --git a/protocols/Twitter/src/stdafx.h b/protocols/Twitter/src/stdafx.h index 5e2d67dd7b..f7bc9d7241 100644 --- a/protocols/Twitter/src/stdafx.h +++ b/protocols/Twitter/src/stdafx.h @@ -62,6 +62,7 @@ using std::map; #define TWITTER_KEY_NICK "Nick" // we need one called Nick for the chat thingo to work #define TWITTER_KEY_UN "Username" +#define TWITTER_KEY_ID "ID" #define TWITTER_KEY_PASS "Password" #define TWITTER_KEY_OAUTH_PIN "OAuthPIN" #define TWITTER_KEY_OAUTH_TOK "OAuthToken" diff --git a/protocols/Twitter/src/twitter.cpp b/protocols/Twitter/src/twitter.cpp index 2a8625e10b..16b105ab67 100644 --- a/protocols/Twitter/src/twitter.cpp +++ b/protocols/Twitter/src/twitter.cpp @@ -108,6 +108,17 @@ void CTwitterProto::remove_friend(const CMStringA &name) Execute(req); } +void CTwitterProto::mark_read(MCONTACT hContact, const CMStringA &msgId) +{ + CMStringA id(getMStringA(hContact, TWITTER_KEY_ID)); + if (id.IsEmpty()) + return; + + auto *req = new AsyncHttpRequest(REQUEST_POST, m_szBaseUrl + "1.1/direct_messages/mark_read.json"); + req << CHAR_PARAM("recipient_id", id) << CHAR_PARAM("last_read_event_id", msgId); + Execute(req); +} + void CTwitterProto::set_status(const CMStringA &text) { if (text.IsEmpty()) diff --git a/protocols/Twitter/src/utility.cpp b/protocols/Twitter/src/utility.cpp index f839bb4838..38452f9608 100644 --- a/protocols/Twitter/src/utility.cpp +++ b/protocols/Twitter/src/utility.cpp @@ -20,34 +20,34 @@ along with this program. If not, see . #include -http::response CTwitterProto::Execute(AsyncHttpRequest *req) +http::response CTwitterProto::Execute(AsyncHttpRequest *pReq) { - if (!req->m_szParam.IsEmpty()) { - if (req->requestType == REQUEST_POST) { - req->AddHeader("Content-Type", "application/x-www-form-urlencoded"); - req->AddHeader("Cache-Control", "no-cache"); + if (!pReq->m_szParam.IsEmpty()) { + if (pReq->requestType == REQUEST_POST) { + pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded"); + pReq->AddHeader("Cache-Control", "no-cache"); - req->dataLength = (int)req->m_szParam.GetLength(); - req->pData = req->m_szParam.GetBuffer(); + pReq->dataLength = (int)pReq->m_szParam.GetLength(); + pReq->pData = pReq->m_szParam.GetBuffer(); } else { - req->m_szUrl.AppendChar('?'); - req->m_szUrl += req->m_szParam; + pReq->m_szUrl.AppendChar('?'); + pReq->m_szUrl += pReq->m_szParam; } } CMStringA auth; - if (req->requestType == REQUEST_GET) - auth = OAuthWebRequestSubmit(req->m_szUrl, "GET", ""); + if (pReq->requestType == REQUEST_GET) + auth = OAuthWebRequestSubmit(pReq->m_szUrl, "GET", ""); else - auth = OAuthWebRequestSubmit(req->m_szUrl, "POST", req->m_szParam); - req->AddHeader("Authorization", auth); + auth = OAuthWebRequestSubmit(pReq->m_szUrl, "POST", pReq->m_szParam); + pReq->AddHeader("Authorization", auth); - req->szUrl = req->m_szUrl.GetBuffer(); - req->flags = NLHRF_HTTP11 | NLHRF_PERSISTENT | NLHRF_REDIRECT; - req->nlc = m_hConnHttp; + pReq->szUrl = pReq->m_szUrl.GetBuffer(); + pReq->flags = NLHRF_HTTP11 | NLHRF_PERSISTENT | NLHRF_REDIRECT; + pReq->nlc = m_hConnHttp; http::response resp_data; - NLHR_PTR resp(Netlib_HttpTransaction(m_hNetlibUser, req)); + NLHR_PTR resp(Netlib_HttpTransaction(m_hNetlibUser, pReq)); if (resp) { debugLogA("**SLURP - the server has responded!"); m_hConnHttp = resp->nlc; @@ -61,6 +61,7 @@ http::response CTwitterProto::Execute(AsyncHttpRequest *req) debugLogA("SLURP - there was no response!"); } + delete pReq; return resp_data; } -- cgit v1.2.3