#include "common.h" WhatsAppProto::WhatsAppProto(const char* proto_name, const TCHAR* username) : PROTO(proto_name, username) { this->challenge = new std::vector; this->msgId = 0; this->msgIdHeader = time(NULL); update_loop_lock_ = CreateEvent(NULL, false, false, NULL); FMessage::generating_lock = new Mutex(); CreateProtoService(PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI); CreateProtoService(PS_JOINCHAT, &WhatsAppProto::OnJoinChat); CreateProtoService(PS_LEAVECHAT, &WhatsAppProto::OnLeaveChat); HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit); HookProtoEvent(ME_SYSTEM_MODULESLOADED, &WhatsAppProto::OnModulesLoaded); HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &WhatsAppProto::OnBuildStatusMenu); this->InitContactMenus(); // Create standard network connection TCHAR descr[512]; NETLIBUSER nlu = {sizeof(nlu)}; nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR; char module[512]; mir_snprintf(module, SIZEOF(module), "%s", m_szModuleName); nlu.szSettingsModule = module; mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); nlu.ptszDescriptiveName = descr; m_hNetlibUser = (HANDLE) CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM) &nlu); if (m_hNetlibUser == NULL) MessageBox(NULL, TranslateT("Unable to get Netlib connection for WhatsApp"), m_tszUserName, MB_OK); WASocketConnection::initNetwork(m_hNetlibUser); def_avatar_folder_ = std::tstring( VARST( _T("%miranda_avatarcache%"))) + _T("\\") + m_tszUserName; SetAllContactStatuses(ID_STATUS_OFFLINE, true); } WhatsAppProto::~WhatsAppProto() { CloseHandle(update_loop_lock_); if (this->challenge != NULL) delete this->challenge; } int WhatsAppProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam) { // Register group chat GCREGISTER gcr = { sizeof(gcr) }; gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR; gcr.ptszDispName = m_tszUserName; gcr.pszModule = m_szModuleName; CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr); HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::OnChatOutgoing); return 0; } DWORD_PTR WhatsAppProto::GetCaps( int type, MCONTACT hContact ) { switch(type) { case PFLAGNUM_1: { DWORD_PTR flags = PF1_IM | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES; return flags | PF1_MODEMSGRECV; // #TODO } case PFLAGNUM_2: return PF2_ONLINE | PF2_INVISIBLE; case PFLAGNUM_3: return 0; case PFLAGNUM_4: return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_FORCEADDED | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_AVATARS; case PFLAGNUM_5: return 0; case PFLAG_MAXLENOFMESSAGE: return 500; // #TODO case PFLAG_UNIQUEIDTEXT: return (DWORD_PTR) "WhatsApp ID"; case PFLAG_UNIQUEIDSETTING: return (DWORD_PTR) "ID"; } return 0; } int WhatsAppProto::SetStatus( int new_status ) { debugLogA("===== Beginning SetStatus process"); // Routing statuses not supported by WhatsApp switch ( new_status ) { case ID_STATUS_INVISIBLE: case ID_STATUS_OFFLINE: m_iDesiredStatus = new_status; break; /* case ID_STATUS_CONNECTING: m_iDesiredStatus = ID_STATUS_OFFLINE; break; */ case ID_STATUS_IDLE: default: m_iDesiredStatus = ID_STATUS_INVISIBLE; if (getByte(WHATSAPP_KEY_MAP_STATUSES, DEFAULT_MAP_STATUSES)) break; case ID_STATUS_ONLINE: case ID_STATUS_FREECHAT: m_iDesiredStatus = ID_STATUS_ONLINE; break; } if (m_iStatus == ID_STATUS_CONNECTING) { debugLogA("===== Status is connecting, no change"); return 0; } if (m_iStatus == m_iDesiredStatus) { debugLogA("===== Statuses are same, no change"); return 0; } ForkThread( &WhatsAppProto::ChangeStatus, this ); return 0; } MCONTACT WhatsAppProto::AddToList(int flags, PROTOSEARCHRESULT* psr) { return NULL; } int WhatsAppProto::AuthRequest(MCONTACT hContact,const PROTOCHAR *message) { return this->RequestFriendship((WPARAM)hContact, NULL); } int WhatsAppProto::Authorize(HANDLE hDbEvent) { return 1; } HANDLE WhatsAppProto::SearchBasic( const PROTOCHAR* id ) { if (isOffline()) return 0; TCHAR* email = mir_tstrdup(id); ForkThread(&WhatsAppProto::SearchAckThread, email); return email; } ///////////////////////////////////////////////////////////////////////////////////////// static NETLIBHTTPHEADER s_registerHeaders[] = { { "User-Agent", ACCOUNT_USER_AGENT_REGISTRATION }, { "Accept", "text/json" }, { "Content-Type", "application/x-www-form-urlencoded" } }; string WhatsAppProto::Register(int state, string cc, string number, string code) { string idx; string ret; DBVARIANT dbv; if ( WASocketConnection::hNetlibUser == NULL) { NotifyEvent(m_tszUserName, TranslateT("Network-connection error."), NULL, WHATSAPP_EVENT_CLIENT); return ret; } if ( !getString(WHATSAPP_KEY_IDX, &dbv)) { idx = dbv.pszVal; db_free(&dbv); } if (idx.empty()) { std::stringstream tm; tm << time(NULL); BYTE idxBuf[16]; utils::md5string(tm.str(), idxBuf); idx = std::string((const char*) idxBuf, 16); setString(WHATSAPP_KEY_IDX, idx.c_str()); } string url; if (state == REG_STATE_REQ_CODE) { unsigned char digest[16]; utils::md5string(std::string(ACCOUNT_TOKEN_PREFIX1) + ACCOUNT_TOKEN_PREFIX2 + number, digest); url = string(ACCOUNT_URL_CODEREQUESTV2); url += "?lc=US&lg=en&mcc=000&mnc=000&method=sms&token=" + Utilities::bytesToHex(digest, 16); } else if (state == REG_STATE_REG_CODE) { url = string(ACCOUNT_URL_REGISTERREQUESTV2); url += "?code="+ code; } url += "&cc="+ cc +"&in="+ number +"&id="+ idx; NETLIBHTTPREQUEST nlhr = {sizeof(NETLIBHTTPREQUEST)}; nlhr.requestType = REQUEST_POST; nlhr.szUrl = (char*) url.c_str(); nlhr.headers = s_registerHeaders; nlhr.headersCount = SIZEOF(s_registerHeaders); nlhr.flags = NLHRF_HTTP11 | NLHRF_GENERATEHOST | NLHRF_REMOVEHOST | NLHRF_SSL; NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*) CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM) WASocketConnection::hNetlibUser, (LPARAM)&nlhr); string title = this->TranslateStr("Registration"); if (pnlhr == NULL) { this->NotifyEvent(title, this->TranslateStr("Registration failed. Invalid server response."), NULL, WHATSAPP_EVENT_CLIENT); return ret; } debugLogA("Server response: %s", pnlhr->pData); MessageBoxA(NULL, pnlhr->pData, "Debug", MB_OK); JSONROOT resp(pnlhr->pData); if (resp == NULL) { this->NotifyEvent(title, this->TranslateStr("Registration failed. Invalid server response."), NULL, WHATSAPP_EVENT_CLIENT); return ret; } // Status = fail JSONNODE *val = json_get(resp, "status"); if (!lstrcmp( ptrT(json_as_string(val)), _T("fail"))) { JSONNODE *tmpVal = json_get(resp, "reason"); if (!lstrcmp( ptrT(json_as_string(tmpVal)), _T("stale"))) this->NotifyEvent(title, this->TranslateStr("Registration failed due to stale code. Please request a new code"), NULL, WHATSAPP_EVENT_CLIENT); else this->NotifyEvent(title, this->TranslateStr("Registration failed."), NULL, WHATSAPP_EVENT_CLIENT); tmpVal = json_get(resp, "retry_after"); if (tmpVal != NULL) this->NotifyEvent(title, this->TranslateStr("Please try again in %i seconds", json_as_int(tmpVal)), NULL, WHATSAPP_EVENT_OTHER); } // Request code else if (state == REG_STATE_REQ_CODE) { if ( !lstrcmp( ptrT(json_as_string(val)), _T("sent"))) this->NotifyEvent(title, this->TranslateStr("Registration code has been sent to your phone."), NULL, WHATSAPP_EVENT_OTHER); } // Register else if (state == REG_STATE_REG_CODE) { val = json_get(resp, "pw"); if (val == NULL) this->NotifyEvent(title, this->TranslateStr("Registration failed."), NULL, WHATSAPP_EVENT_CLIENT); else ret = _T2A( ptrT(json_as_string(val))); } json_delete(resp); return ret; } ////////////////////////////////////////////////////////////////////////////// // EVENTS INT_PTR WhatsAppProto::SvcCreateAccMgrUI(WPARAM wParam,LPARAM lParam) { return (INT_PTR)CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_WHATSAPPACCOUNT), (HWND)lParam, WhatsAppAccountProc, (LPARAM)this ); } int WhatsAppProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) { OPTIONSDIALOGPAGE odp = {sizeof(odp)}; odp.hInstance = g_hInstance; odp.ptszTitle = m_tszUserName; odp.dwInitParam = LPARAM(this); odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_DONTTRANSLATE; odp.position = 1; odp.ptszGroup = LPGENT("Network"); odp.ptszTab = LPGENT("Account"); odp.pszTemplate = MAKEINTRESOURCEA(IDD_WHATSAPOPTIONS); odp.pfnDlgProc = WhatsAppAccountProc; Options_AddPage(wParam, &odp); return 0; } int WhatsAppProto::RefreshBuddyList(WPARAM, LPARAM ) { debugLogA(""); if (!isOffline()) { //ForkThread( } return 0; } int WhatsAppProto::RequestFriendship(WPARAM wParam, LPARAM lParam) { if (wParam == NULL || isOffline()) return 0; MCONTACT hContact = MCONTACT(wParam); DBVARIANT dbv; if ( !getString(hContact, WHATSAPP_KEY_ID, &dbv)) { std::string id(dbv.pszVal); this->connection->sendQueryLastOnline(id); this->connection->sendPresenceSubscriptionRequest(id); db_free(&dbv); } return 0; } std::tstring WhatsAppProto::GetAvatarFolder() { return def_avatar_folder_; } LRESULT CALLBACK PopupDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_COMMAND: { // After a click, destroy popup PUDeletePopup(hwnd); } break; case WM_CONTEXTMENU: PUDeletePopup(hwnd); break; case UM_FREEPLUGINDATA: { // After close, free TCHAR* url = (TCHAR*)PUGetPluginData(hwnd); if (url != NULL) mir_free(url); } return FALSE; default: break; } return DefWindowProc(hwnd, message, wParam, lParam); }; void WhatsAppProto::NotifyEvent(const string& title, const string& info, MCONTACT contact, DWORD flags, TCHAR* url) { TCHAR* rawTitle = mir_a2t_cp(title.c_str(), CP_UTF8); TCHAR* rawInfo = mir_a2t_cp(info.c_str(), CP_UTF8); this->NotifyEvent(rawTitle, rawInfo, contact, flags, url); mir_free(rawTitle); mir_free(rawInfo); } void WhatsAppProto::NotifyEvent(TCHAR* title, TCHAR* info, MCONTACT contact, DWORD flags, TCHAR* szUrl) { int ret; int timeout; COLORREF colorBack = 0; COLORREF colorText = 0; switch ( flags ) { case WHATSAPP_EVENT_CLIENT: if ( !getByte( WHATSAPP_KEY_EVENT_CLIENT_ENABLE, DEFAULT_EVENT_CLIENT_ENABLE )) goto exit; if ( !getByte( WHATSAPP_KEY_EVENT_CLIENT_DEFAULT, 0 )) { colorBack = getDword( WHATSAPP_KEY_EVENT_CLIENT_COLBACK, DEFAULT_EVENT_COLBACK ); colorText = getDword( WHATSAPP_KEY_EVENT_CLIENT_COLTEXT, DEFAULT_EVENT_COLTEXT ); } timeout = getDword( WHATSAPP_KEY_EVENT_CLIENT_TIMEOUT, 0 ); flags |= NIIF_WARNING; break; case WHATSAPP_EVENT_OTHER: if ( !getByte( WHATSAPP_KEY_EVENT_OTHER_ENABLE, DEFAULT_EVENT_OTHER_ENABLE )) goto exit; if ( !getByte( WHATSAPP_KEY_EVENT_OTHER_DEFAULT, 0 )) { colorBack = getDword( WHATSAPP_KEY_EVENT_OTHER_COLBACK, DEFAULT_EVENT_COLBACK ); colorText = getDword( WHATSAPP_KEY_EVENT_OTHER_COLTEXT, DEFAULT_EVENT_COLTEXT ); } timeout = getDword( WHATSAPP_KEY_EVENT_OTHER_TIMEOUT, -1 ); SkinPlaySound( "OtherEvent" ); flags |= NIIF_INFO; break; } if ( !getByte(WHATSAPP_KEY_SYSTRAY_NOTIFY,DEFAULT_SYSTRAY_NOTIFY)) { if (ServiceExists(MS_POPUP_ADDPOPUP)) { POPUPDATAT pd; pd.colorBack = colorBack; pd.colorText = colorText; pd.iSeconds = timeout; pd.lchContact = contact; pd.lchIcon = Skin_GetIconByHandle(m_hProtoIcon); // TODO: Icon test pd.PluginData = szUrl; pd.PluginWindowProc = (WNDPROC)PopupDlgProc; lstrcpy(pd.lptzContactName, title); lstrcpy(pd.lptzText, info); ret = PUAddPopupT(&pd); if (ret == 0) return; } } else { if (ServiceExists(MS_CLIST_SYSTRAY_NOTIFY)) { MIRANDASYSTRAYNOTIFY err; int niif_flags = flags; REMOVE_FLAG( niif_flags, WHATSAPP_EVENT_CLIENT | WHATSAPP_EVENT_NOTIFICATION | WHATSAPP_EVENT_OTHER ); err.szProto = m_szModuleName; err.cbSize = sizeof(err); err.dwInfoFlags = NIIF_INTERN_TCHAR | niif_flags; err.tszInfoTitle = title; err.tszInfo = info; err.uTimeout = 1000 * timeout; ret = CallService(MS_CLIST_SYSTRAY_NOTIFY, 0, (LPARAM) & err); if (ret == 0) goto exit; } } if (FLAG_CONTAINS(flags, WHATSAPP_EVENT_CLIENT)) MessageBox(NULL, info, title, MB_OK | MB_ICONINFORMATION); exit: if (szUrl != NULL) mir_free(szUrl); } string WhatsAppProto::TranslateStr(const char* str, ...) { va_list ap; va_start(ap, str); string ret = Utilities::string_format(Translate(str), ap); va_end(ap); return ret; }