From a6fb825ecbf5313b010fd8cf37754f494cabddc5 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Tue, 5 Jun 2012 17:56:48 +0000 Subject: Twitter plugin git-svn-id: http://svn.miranda-ng.org/main/trunk@316 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/Twitter/proto.cpp | 528 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 528 insertions(+) create mode 100644 plugins/Twitter/proto.cpp (limited to 'plugins/Twitter/proto.cpp') diff --git a/plugins/Twitter/proto.cpp b/plugins/Twitter/proto.cpp new file mode 100644 index 0000000000..44bd6a3ebc --- /dev/null +++ b/plugins/Twitter/proto.cpp @@ -0,0 +1,528 @@ +/* +Copyright © 2009 Jim Porter + +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 "common.h" +#include "proto.h" + +#include "utility.h" +#include "theme.h" +#include "ui.h" + +#include "m_folders.h" +#include "m_historyevents.h" + +#include +#include +#include +#include + +TwitterProto::TwitterProto(const char *proto_name, const TCHAR *username) +{ + m_szProtoName = mir_strdup (proto_name); + m_szModuleName = mir_strdup (proto_name); + m_tszUserName = mir_tstrdup(username); + + CreateProtoService(m_szModuleName, PS_CREATEACCMGRUI, + &TwitterProto::SvcCreateAccMgrUI, this); + CreateProtoService(m_szModuleName, PS_GETNAME, &TwitterProto::GetName, this); + CreateProtoService(m_szModuleName, PS_GETSTATUS, &TwitterProto::GetStatus, this); + + CreateProtoService(m_szModuleName, PS_JOINCHAT, &TwitterProto::OnJoinChat, this); + CreateProtoService(m_szModuleName, PS_LEAVECHAT, &TwitterProto::OnLeaveChat, this); + + CreateProtoService(m_szModuleName, PS_GETMYAVATAR, &TwitterProto::GetAvatar, this); + CreateProtoService(m_szModuleName, PS_SETMYAVATAR, &TwitterProto::SetAvatar, this); + + HookProtoEvent(ME_DB_CONTACT_DELETED, &TwitterProto::OnContactDeleted, this); + HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &TwitterProto::OnBuildStatusMenu, this); + HookProtoEvent(ME_OPT_INITIALISE, &TwitterProto::OnOptionsInit, this); + + char *profile = Utils_ReplaceVars("%miranda_avatarcache%"); + def_avatar_folder_ = std::string(profile)+"\\"+m_szModuleName; + mir_free(profile); + hAvatarFolder_ = FoldersRegisterCustomPath(m_szModuleName, "Avatars", + def_avatar_folder_.c_str()); + + // Initialize hotkeys + char text[512]; + HOTKEYDESC hkd = {sizeof(hkd)}; + hkd.cbSize = sizeof(hkd); + hkd.pszName = text; + hkd.pszService = text; + hkd.pszSection = m_szModuleName; // Section title; TODO: use username? + + mir_snprintf(text, SIZEOF(text), "%s/Tweet", m_szModuleName); + hkd.pszDescription = "Send Tweet"; + CallService(MS_HOTKEY_REGISTER, 0, (LPARAM)&hkd); + + signon_lock_ = CreateMutex(0, false, 0); + avatar_lock_ = CreateMutex(0, false, 0); + twitter_lock_ = CreateMutex(0, false, 0); + + SetAllContactStatuses(ID_STATUS_OFFLINE); // In case we crashed last time +} + +TwitterProto::~TwitterProto() +{ + CloseHandle(twitter_lock_); + CloseHandle(avatar_lock_); + CloseHandle(signon_lock_); + + mir_free(m_szProtoName); + mir_free(m_szModuleName); + mir_free(m_tszUserName); + + if (hNetlib_) + Netlib_CloseHandle(hNetlib_); + if (hAvatarNetlib_) + Netlib_CloseHandle(hAvatarNetlib_); +} + +// ************************* + +DWORD TwitterProto::GetCaps(int type, HANDLE hContact) +{ + switch(type) + { + case PFLAGNUM_1: + return PF1_IM | PF1_MODEMSGRECV | PF1_BASICSEARCH | PF1_SEARCHBYEMAIL | + PF1_SERVERCLIST | PF1_CHANGEINFO; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_3: + return PF2_ONLINE; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_AVATARS; + case PFLAG_MAXLENOFMESSAGE: + return 140; + case PFLAG_UNIQUEIDTEXT: + return (int) "Username"; + case PFLAG_UNIQUEIDSETTING: + return (int) TWITTER_KEY_UN; + } + return 0; +} + +HICON TwitterProto::GetIcon(int index) +{ + if (LOWORD(index) == PLI_PROTOCOL) + { + HICON ico = (HICON)CallService(MS_SKIN2_GETICON, 0, (LPARAM)"Twitter_twitter"); + return CopyIcon(ico); + } + else + return 0; +} + +// ************************* + +int TwitterProto::RecvMsg(HANDLE hContact, PROTORECVEVENT *pre) +{ + CCSDATA ccs = { hContact, PSR_MESSAGE, 0, reinterpret_cast(pre) }; + return CallService(MS_PROTO_RECVMSG, 0, reinterpret_cast(&ccs)); +} + +// ************************* + +struct send_direct +{ + send_direct(HANDLE hContact, const std::tstring &msg) : hContact(hContact), msg(msg) {} + HANDLE hContact; + std::tstring msg; +}; + +void TwitterProto::SendSuccess(void *p) +{ + if (p == 0) + return; + send_direct *data = static_cast(p); + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(data->hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + { + ScopedLock s(twitter_lock_); + twit_.send_direct(dbv.ptszVal, data->msg); + + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, + (HANDLE)1, 0); + DBFreeVariant(&dbv); + } + + delete data; +} + +int TwitterProto::SendMsg(HANDLE hContact, int flags, const char *msg) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 0; + + std::tstring tMsg = _A2T(msg); + ForkThread(&TwitterProto::SendSuccess, this, new send_direct(hContact, tMsg)); + return 1; +} + +// ************************* + +int TwitterProto::SetStatus(int new_status) +{ + int old_status = m_iStatus; + if (new_status == m_iStatus) + return 0; + + m_iDesiredStatus = new_status; + + if (new_status == ID_STATUS_ONLINE) + { + if (old_status == ID_STATUS_CONNECTING) + return 0; + + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + + ForkThread(&TwitterProto::SignOn, this); + } + else if (new_status == ID_STATUS_OFFLINE) + { + m_iStatus = m_iDesiredStatus; + SetAllContactStatuses(ID_STATUS_OFFLINE); + + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + } + + return 0; +} + +// ************************* + +int TwitterProto::OnEvent(PROTOEVENTTYPE event, WPARAM wParam, LPARAM lParam) +{ + switch(event) + { + case EV_PROTO_ONLOAD: return OnModulesLoaded(wParam, lParam); + case EV_PROTO_ONEXIT: return OnPreShutdown (wParam, lParam); + case EV_PROTO_ONOPTIONS: return OnOptionsInit (wParam, lParam); + } + + return 1; +} + +// ************************* + +int TwitterProto::SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam) +{ + return (int)CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWITTERACCOUNT), + (HWND)lParam, first_run_dialog, (LPARAM)this ); +} + +int TwitterProto::GetName(WPARAM wParam, LPARAM lParam) +{ + lstrcpynA(reinterpret_cast(lParam), m_szProtoName, wParam); + return 0; +} + +int TwitterProto::GetStatus(WPARAM wParam, LPARAM lParam) +{ + return m_iStatus; +} + +int TwitterProto::ReplyToTweet(WPARAM wParam, LPARAM lParam) +{ + // TODO: support replying to tweets instead of just users + HANDLE hContact = reinterpret_cast(wParam); + + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), + (HWND)0, tweet_proc, reinterpret_cast(this)); + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + SendMessage(hDlg, WM_SETREPLY, reinterpret_cast(dbv.ptszVal), 0); + DBFreeVariant(&dbv); + } + + ShowWindow(hDlg, SW_SHOW); + + return 0; +} + +int TwitterProto::VisitHomepage(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = reinterpret_cast(wParam); + + DBVARIANT dbv; + if ( !DBGetContactSettingString(hContact, m_szModuleName, "Homepage", &dbv)) { + CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(dbv.pszVal)); + DBFreeVariant(&dbv); + } + else { + // TODO: remove this + if ( !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + std::string url = profile_base_url(twit_.get_base_url()) + http::url_encode(dbv.pszVal); + DBWriteContactSettingString(hContact, m_szModuleName, "Homepage", url.c_str()); + + CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(url.c_str())); + DBFreeVariant(&dbv); + } + } + + return 0; +} + +// ************************* + +int TwitterProto::OnBuildStatusMenu(WPARAM wParam, LPARAM lParam) +{ + HGENMENU hRoot = pcli->pfnGetProtocolMenu(m_szModuleName); + if (hRoot == NULL) + return 0; + + CLISTMENUITEM mi = {sizeof(mi)}; + + char text[200]; + strcpy(text, m_szModuleName); + char *tDest = text+strlen(text); + mi.pszService = text; + + mi.hParentMenu = hRoot; + mi.flags = CMIF_ICONFROMICOLIB|CMIF_ROOTHANDLE; + mi.position = 1001; + + HANDLE m_hMenuRoot = reinterpret_cast( CallService( + MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi))); + + // "Send Tweet..." + CreateProtoService(m_szModuleName, "/Tweet", &TwitterProto::OnTweet, this); + strcpy(tDest, "/Tweet"); + mi.pszName = LPGEN("Send Tweet..."); + mi.popupPosition = 200001; + mi.icolibItem = GetIconHandle("tweet"); + HANDLE m_hMenuBookmarks = reinterpret_cast( CallService( + MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi))); + + return 0; +} + +int TwitterProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp = {sizeof(odp)}; + odp.position = 271828; + odp.hInstance = g_hInstance; + odp.ptszGroup = LPGENT("Network"); + odp.ptszTitle = m_tszUserName; + odp.dwInitParam = LPARAM(this); + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; + + odp.ptszTab = LPGENT("Basic"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS); + odp.pfnDlgProc = options_proc; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + { + odp.ptszTab = LPGENT("Popups"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_POPUPS); + odp.pfnDlgProc = popup_options_proc; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + } + + return 0; +} + +int TwitterProto::OnTweet(WPARAM wParam, LPARAM lParam) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 1; + + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), + (HWND)0, tweet_proc, reinterpret_cast(this)); + ShowWindow(hDlg, SW_SHOW); + return 0; +} + +int TwitterProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam) +{ + TCHAR descr[512]; + NETLIBUSER nlu = {sizeof(nlu)}; + nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_TCHAR; + nlu.szSettingsModule = m_szModuleName; + + // Create standard network connection + mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr; + hNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + if (hNetlib_ == 0) + MessageBoxA(0, "Unable to get Netlib connection for Twitter", "", 0); + + // Create avatar network connection (TODO: probably remove this) + char module[512]; + mir_snprintf(module, SIZEOF(module), "%sAv", m_szModuleName); + nlu.szSettingsModule = module; + mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s avatar connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr; + hAvatarNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + if (hAvatarNetlib_ == 0) + MessageBoxA(0, "Unable to get avatar Netlib connection for Twitter", "", 0); + + twit_.set_handle(hNetlib_); + + GCREGISTER gcr = {sizeof(gcr)}; + gcr.pszModule = m_szModuleName; + gcr.pszModuleDispName = m_szModuleName; + gcr.iMaxText = 140; + CallService(MS_GC_REGISTER, 0, reinterpret_cast(&gcr)); + + if (ServiceExists(MS_HISTORYEVENTS_REGISTER)) + { + HISTORY_EVENT_HANDLER heh = {0}; + heh.cbSize = sizeof(heh); + heh.module = m_szModuleName; + heh.name = "tweet"; + heh.description = "Tweet"; + heh.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + heh.defaultIconName = "Twitter_tweet"; + heh.flags = HISTORYEVENTS_FLAG_SHOW_IM_SRMM + | HISTORYEVENTS_FLAG_EXPECT_CONTACT_NAME_BEFORE +// Not sure: | HISTORYEVENTS_FLAG_FLASH_MSG_WINDOW + | HISTORYEVENTS_REGISTERED_IN_ICOLIB; + CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0); + } + else + { + DBEVENTTYPEDESCR evt = {sizeof(evt)}; + evt.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + evt.module = m_szModuleName; + evt.descr = "Tweet"; + evt.flags = DETF_HISTORY | DETF_MSGWINDOW; + CallService(MS_DB_EVENT_REGISTERTYPE, 0, reinterpret_cast(&evt)); + } + + return 0; +} + +int TwitterProto::OnPreShutdown(WPARAM wParam, LPARAM lParam) +{ + Netlib_Shutdown(hNetlib_); + Netlib_Shutdown(hAvatarNetlib_); + return 0; +} + +int TwitterProto::OnPrebuildContactMenu(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = reinterpret_cast(wParam); + if (IsMyContact(hContact)) + ShowContactMenus(true); + + return 0; +} + +void TwitterProto::ShowPopup(const wchar_t *text) +{ + POPUPDATAT popup = {}; + _sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName); + wcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0); + else + MessageBox(0, popup.lptzText, popup.lptzContactName, 0); +} + +void TwitterProto::ShowPopup(const char *text) +{ + POPUPDATAT popup = {}; + _sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName); + mbcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0); + else + MessageBox(0, popup.lptzText, popup.lptzContactName, 0); +} + +int TwitterProto::LOG(const char *fmt, ...) +{ + va_list va; + char text[1024]; + if (!hNetlib_) + return 0; + + va_start(va, fmt); + mir_vsnprintf(text, sizeof(text), fmt, va); + va_end(va); + + return CallService(MS_NETLIB_LOG, (WPARAM)hNetlib_, (LPARAM)text); +} + +// TODO: the more I think about it, the more I think all twit.* methods should +// be in MessageLoop +void TwitterProto::SendTweetWorker(void *p) +{ + if (p == 0) + return; + + TCHAR *text = static_cast(p); + + ScopedLock s(twitter_lock_); + twit_.set_status(text); + + mir_free(text); +} + +void TwitterProto::UpdateSettings() +{ + if (db_byte_get(0, m_szModuleName, TWITTER_KEY_CHATFEED, 0)) + { + if (!in_chat_) + OnJoinChat(0, 0); + } + else + { + if (in_chat_) + OnLeaveChat(0, 0); + + for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) + { + if (!IsMyContact(hContact, true)) + continue; + + if (db_byte_get(hContact, m_szModuleName, "ChatRoom", 0)) + CallService(MS_DB_CONTACT_DELETE, reinterpret_cast(hContact), 0); + } + } +} + +std::string TwitterProto::GetAvatarFolder() +{ + char path[MAX_PATH]; + if (hAvatarFolder_ && FoldersGetCustomPath(hAvatarFolder_, path, sizeof(path), "") == 0) + return path; + else + return def_avatar_folder_; +} + +int TwitterProto::GetAvatar(WPARAM, LPARAM) +{ + return 0; +} + +int TwitterProto::SetAvatar(WPARAM, LPARAM) +{ + return 0; +} \ No newline at end of file -- cgit v1.2.3