/* Plugin of Miranda IM for communicating with users of the AIM protocol. Copyright (c) 2008-2012 Boris Krasnovskiy 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 "aim.h" CAimProto::CAimProto(const char* aProtoName, const TCHAR* aUserName) : chat_rooms(5) { ProtoConstructor(this, aProtoName, aUserName); LOG("Setting protocol/module name to '%s'", m_szModuleName); //create some events hAvatarEvent = CreateEvent(NULL, TRUE, FALSE, NULL); hChatNavEvent = CreateEvent(NULL, TRUE, FALSE, NULL); hAdminEvent = CreateEvent(NULL, TRUE, FALSE, NULL); InitializeCriticalSection(&SendingMutex); InitializeCriticalSection(&connMutex); CreateProtoService(PS_CREATEACCMGRUI, &CAimProto::SvcCreateAccMgrUI); CreateProtoService(PS_GETMYAWAYMSG, &CAimProto::GetMyAwayMsg); CreateProtoService(PS_GETAVATARINFOT, &CAimProto::GetAvatarInfo); CreateProtoService(PS_GETMYAVATART, &CAimProto::GetAvatar); CreateProtoService(PS_SETMYAVATART, &CAimProto::SetAvatar); CreateProtoService(PS_GETAVATARCAPS, &CAimProto::GetAvatarCaps); CreateProtoService(PS_JOINCHAT, &CAimProto::OnJoinChat); CreateProtoService(PS_LEAVECHAT, &CAimProto::OnLeaveChat); HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CAimProto::OnPreBuildContactMenu); HookProtoEvent(ME_CLIST_GROUPCHANGE, &CAimProto::OnGroupChange); HookProtoEvent(ME_OPT_INITIALISE, &CAimProto::OnOptionsInit); init_custom_folders(); offline_contacts(); TCHAR descr[MAX_PATH]; NETLIBUSER nlu = {0}; nlu.cbSize = sizeof(nlu); nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR; nlu.szSettingsModule = m_szModuleName; mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); nlu.ptszDescriptiveName = descr; hNetlib = (HANDLE) CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); char szP2P[128]; mir_snprintf(szP2P, sizeof(szP2P), "%sP2P", m_szModuleName); nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_TCHAR; mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s Client-to-client connection"), m_tszUserName); nlu.szSettingsModule = szP2P; nlu.minIncomingPorts = 1; hNetlibPeer = (HANDLE) CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); } CAimProto::~CAimProto() { RemoveMainMenus(); RemoveContactMenus(); if (hServerConn) Netlib_CloseHandle(hServerConn); if (hAvatarConn && hAvatarConn != (HANDLE)1) Netlib_CloseHandle(hAvatarConn); if (hChatNavConn && hChatNavConn != (HANDLE)1) Netlib_CloseHandle(hChatNavConn); if (hAdminConn && hAdminConn != (HANDLE)1) Netlib_CloseHandle(hAdminConn); close_chat_conn(); Netlib_CloseHandle(hNetlib); Netlib_CloseHandle(hNetlibPeer); DeleteCriticalSection(&SendingMutex); DeleteCriticalSection(&connMutex); CloseHandle(hAvatarEvent); CloseHandle(hChatNavEvent); CloseHandle(hAdminEvent); ft_list.destroy(); for (int i=0; i<9; ++i) mir_free(modeMsgs[i]); mir_free(pref2_flags); mir_free(pref2_set_flags); mir_free(COOKIE); mir_free(MAIL_COOKIE); mir_free(AVATAR_COOKIE); mir_free(CHATNAV_COOKIE); mir_free(ADMIN_COOKIE); mir_free(username); ProtoDestructor(this); } //////////////////////////////////////////////////////////////////////////////////////// // OnModulesLoadedEx - performs hook registration int CAimProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam) { HookProtoEvent(ME_USERINFO_INITIALISE, &CAimProto::OnUserInfoInit); HookProtoEvent(ME_IDLE_CHANGED, &CAimProto::OnIdleChanged); HookProtoEvent(ME_MSG_WINDOWEVENT, &CAimProto::OnWindowEvent); chat_register(); InitContactMenus(); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // AddToList - adds a contact to the contact list HANDLE CAimProto::AddToList(int flags, PROTOSEARCHRESULT* psr) { if (state != 1) return 0; TCHAR *id = psr->id ? psr->id : psr->nick; char *sn = psr->flags & PSR_UNICODE ? mir_u2a((wchar_t*)id) : mir_strdup((char*)id); HANDLE hContact = contact_from_sn(sn, true, (flags & PALF_TEMPORARY) != 0); mir_free(sn); return hContact; //See authrequest for serverside addition } HANDLE __cdecl CAimProto::AddToListByEvent(int flags, int iContact, HANDLE hDbEvent) { return NULL; } //////////////////////////////////////////////////////////////////////////////////////// // AuthAllow - processes the successful authorization int CAimProto::Authorize(HANDLE hDbEvent) { return 0; } //////////////////////////////////////////////////////////////////////////////////////// // AuthDeny - handles the unsuccessful authorization int CAimProto::AuthDeny(HANDLE hDbEvent, const TCHAR* szReason) { return 0; } //////////////////////////////////////////////////////////////////////////////////////// // PSR_AUTH int __cdecl CAimProto::AuthRecv(HANDLE hContact, PROTORECVEVENT* evt) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // PSS_AUTHREQUEST int __cdecl CAimProto::AuthRequest(HANDLE hContact, const TCHAR* szMessage) { //Not a real authrequest- only used b/c we don't know the group until now. if (state != 1) return 1; DBVARIANT dbv; if (!db_get_utf(hContact, MOD_KEY_CL, OTH_KEY_GP, &dbv) && dbv.pszVal[0]) { add_contact_to_group(hContact, dbv.pszVal); db_free(&dbv); } else add_contact_to_group(hContact, AIM_DEFAULT_GROUP); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // ChangeInfo HANDLE __cdecl CAimProto::ChangeInfo(int iInfoType, void* pInfoData) { return NULL; } //////////////////////////////////////////////////////////////////////////////////////// // FileAllow - starts a file transfer HANDLE __cdecl CAimProto::FileAllow(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath) { file_transfer *ft = (file_transfer*)hTransfer; if (ft && ft_list.find_by_ft(ft)) { char *path = mir_utf8encodeT(szPath); if (ft->pfts.totalFiles > 1 && ft->file[0]) { size_t path_len = strlen(path); size_t len = strlen(ft->file) + 2; path = (char*)mir_realloc(path, path_len + len); mir_snprintf(&path[path_len], len, "%s\\", ft->file); } mir_free(ft->file); ft->file = path; ForkThread(&CAimProto::accept_file_thread, ft); return ft; } return 0; } //////////////////////////////////////////////////////////////////////////////////////// // FileCancel - cancels a file transfer int __cdecl CAimProto::FileCancel(HANDLE hContact, HANDLE hTransfer) { file_transfer *ft = (file_transfer*)hTransfer; if (!ft_list.find_by_ft(ft)) return 0; LOG("We are cancelling a file transfer."); aim_chat_deny(hServerConn, seqno, ft->sn, ft->icbm_cookie); if (ft->hConn) { Netlib_Shutdown(ft->hConn); SetEvent(ft->hResumeEvent); } else ft_list.remove_by_ft(ft); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // FileDeny - denies a file transfer int __cdecl CAimProto::FileDeny(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* /*szReason*/) { file_transfer *ft = (file_transfer*)hTransfer; if (!ft_list.find_by_ft(ft)) return 0; LOG("We are denying a file transfer."); aim_chat_deny(hServerConn, seqno, ft->sn, ft->icbm_cookie); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // FileResume - processes file renaming etc int __cdecl CAimProto::FileResume(HANDLE hTransfer, int* action, const PROTOCHAR** szFilename) { file_transfer *ft = (file_transfer*)hTransfer; if (!ft_list.find_by_ft(ft)) return 0; switch (*action) { case FILERESUME_RESUME: { struct _stati64 statbuf; _tstati64(ft->pfts.tszCurrentFile, &statbuf); ft->pfts.currentFileProgress = statbuf.st_size; } break; case FILERESUME_RENAME: mir_free(ft->pfts.tszCurrentFile); ft->pfts.tszCurrentFile = mir_tstrdup(*szFilename); break; case FILERESUME_OVERWRITE: ft->pfts.currentFileProgress = 0; break; case FILERESUME_SKIP: mir_free(ft->pfts.tszCurrentFile); ft->pfts.tszCurrentFile = NULL; break; default: aim_file_ad(hServerConn, seqno, ft->sn, ft->icbm_cookie, true, ft->max_ver); break; } SetEvent(ft->hResumeEvent); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // GetCaps - return protocol capabilities bits DWORD_PTR __cdecl CAimProto::GetCaps(int type, HANDLE hContact) { switch (type) { case PFLAGNUM_1: return PF1_IM | PF1_MODEMSG | PF1_BASICSEARCH | PF1_SEARCHBYEMAIL | PF1_FILE; case PFLAGNUM_2: #ifdef ALLOW_BUSY return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_ONTHEPHONE | PF2_LIGHTDND; #else return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_ONTHEPHONE; #endif case PFLAGNUM_3: #ifdef ALLOW_BUSY return PF2_ONLINE | PF2_SHORTAWAY | PF2_INVISIBLE | PF2_LIGHTDND; #else return PF2_ONLINE | PF2_SHORTAWAY | PF2_INVISIBLE; #endif case PFLAGNUM_4: return PF4_SUPPORTTYPING | PF4_FORCEAUTH | PF4_NOCUSTOMAUTH | PF4_FORCEADDED | PF4_SUPPORTIDLE | PF4_AVATARS | PF4_IMSENDUTF | PF4_IMSENDOFFLINE; case PFLAGNUM_5: return PF2_ONTHEPHONE; case PFLAG_MAXLENOFMESSAGE: return MAX_MESSAGE_LENGTH; case PFLAG_UNIQUEIDTEXT: return (DWORD_PTR) "Screen Name"; case PFLAG_UNIQUEIDSETTING: return (DWORD_PTR) AIM_KEY_SN; } return 0; } //////////////////////////////////////////////////////////////////////////////////////// // GetInfo - retrieves a contact info int __cdecl CAimProto::GetInfo(HANDLE hContact, int infoType) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // SearchBasic - searches the contact by JID void __cdecl CAimProto::basic_search_ack_success(void* p) { char *sn = normalize_name((char*)p); if (sn) // normalize it { if (strlen(sn) > 32) { ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE) 1, 0); } else { PROTOSEARCHRESULT psr = {0}; psr.cbSize = sizeof(psr); psr.id = (TCHAR*)sn; ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE) 1, (LPARAM) & psr); ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE) 1, 0); } } mir_free(sn); mir_free(p); } HANDLE __cdecl CAimProto::SearchBasic(const PROTOCHAR* szId) { if (state != 1) return 0; //duplicating the parameter so that it isn't deleted before it's needed- e.g. this function ends before it's used ForkThread(&CAimProto::basic_search_ack_success, mir_t2a(szId)); return (HANDLE)1; } //////////////////////////////////////////////////////////////////////////////////////// // SearchByEmail - searches the contact by its e-mail HANDLE __cdecl CAimProto::SearchByEmail(const PROTOCHAR* email) { // Maximum email size should really be 320, but the char string is limited to 255. if (state != 1 || email == NULL || _tcslen(email) >= 254) return NULL; char* szEmail = mir_t2a(email); aim_search_by_email(hServerConn, seqno, szEmail); mir_free(szEmail); return (HANDLE)1; } //////////////////////////////////////////////////////////////////////////////////////// // SearchByName - searches the contact by its first or last name, or by a nickname HANDLE __cdecl CAimProto::SearchByName(const PROTOCHAR* nick, const PROTOCHAR* firstName, const PROTOCHAR* lastName) { return NULL; } HWND __cdecl CAimProto::SearchAdvanced(HWND owner) { return NULL; } HWND __cdecl CAimProto::CreateExtendedSearchUI(HWND owner) { return NULL; } //////////////////////////////////////////////////////////////////////////////////////// // RecvContacts int __cdecl CAimProto::RecvContacts(HANDLE hContact, PROTORECVEVENT*) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // RecvFile int __cdecl CAimProto::RecvFile(HANDLE hContact, PROTOFILEEVENT* evt) { return Proto_RecvFile(hContact, evt); } //////////////////////////////////////////////////////////////////////////////////////// // RecvMsg int __cdecl CAimProto::RecvMsg(HANDLE hContact, PROTORECVEVENT* pre) { char *omsg = pre->szMessage; char *bbuf = NULL; if (getByte(AIM_KEY_FI, 1)) { LOG("Converting from html to bbcodes then stripping leftover html."); pre->szMessage = bbuf = html_to_bbcodes(pre->szMessage); } LOG("Stripping html."); html_decode(pre->szMessage); INT_PTR res = Proto_RecvMessage(hContact, pre); mir_free(bbuf); pre->szMessage = omsg; return ( int )res; } //////////////////////////////////////////////////////////////////////////////////////// // RecvUrl int __cdecl CAimProto::RecvUrl(HANDLE hContact, PROTORECVEVENT*) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // SendContacts int __cdecl CAimProto::SendContacts(HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // SendFile - sends a file HANDLE __cdecl CAimProto::SendFile(HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles) { if (state != 1) return 0; if (hContact && szDescription && ppszFiles) { DBVARIANT dbv; if (!getString(hContact, AIM_KEY_SN, &dbv)) { file_transfer *ft = new file_transfer(hContact, dbv.pszVal, NULL); bool isDir = false; int count = 0; while (ppszFiles[count] != NULL) { struct _stati64 statbuf; if (_tstati64(ppszFiles[count++], &statbuf) == 0) { if (statbuf.st_mode & _S_IFDIR) { if (ft->pfts.totalFiles == 0) isDir = true; } else { ft->pfts.totalBytes += statbuf.st_size; ++ft->pfts.totalFiles; } } } if (ft->pfts.totalFiles == 0) { delete ft; return NULL; } ft->pfts.flags |= PFTS_SENDING; ft->pfts.ptszFiles = ppszFiles; ft->file = ft->pfts.totalFiles == 1 || isDir ? mir_utf8encodeT(ppszFiles[0]) : (char*)mir_calloc(1); ft->sending = true; ft->message = szDescription[0] ? mir_utf8encodeT(szDescription) : NULL; ft->me_force_proxy = getByte(AIM_KEY_FP, 0) != 0; ft->requester = true; ft_list.insert(ft); if (ft->me_force_proxy) { LOG("We are forcing a proxy file transfer."); ForkThread(&CAimProto::accept_file_thread, ft); } else { ft->listen(this); aim_send_file(hServerConn, seqno, detected_ip, ft->local_port, false, ft); } db_free(&dbv); return ft; } } return NULL; } //////////////////////////////////////////////////////////////////////////////////////// // SendMessage - sends a message void __cdecl CAimProto::msg_ack_success(void* param) { msg_ack_param *msg_ack = (msg_ack_param*)param; Sleep(150); ProtoBroadcastAck(msg_ack->hContact, ACKTYPE_MESSAGE, msg_ack->success ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)msg_ack->id, (LPARAM)msg_ack->msg); mir_free(msg_ack); } int __cdecl CAimProto::SendMsg(HANDLE hContact, int flags, const char* pszSrc) { if (pszSrc == NULL) return 0; if (state != 1) { msg_ack_param *msg_ack = (msg_ack_param*)mir_calloc(sizeof(msg_ack_param)); msg_ack->hContact = hContact; msg_ack->msg = "Message cannot be sent, when protocol offline"; ForkThread(&CAimProto::msg_ack_success, msg_ack); } char *sn = getSetting(hContact, AIM_KEY_SN); if (sn == NULL) { msg_ack_param *msg_ack = (msg_ack_param*)mir_calloc(sizeof(msg_ack_param)); msg_ack->hContact = hContact; msg_ack->msg = "Screen Name for the contact not available"; ForkThread(&CAimProto::msg_ack_success, msg_ack); } char* msg; if (flags & PREF_UNICODE) { const char* p = strchr(pszSrc, '\0'); if (p != pszSrc) { while (*(++p) == '\0'); } msg = mir_utf8encodeW((wchar_t*)p); } else if (flags & PREF_UTF) msg = mir_strdup(pszSrc); else msg = mir_utf8encode(pszSrc); char* smsg = html_encode(msg); mir_free(msg); if (getByte(AIM_KEY_FO, 1)) { msg = bbcodes_to_html(smsg); mir_free(smsg); } else msg = smsg; bool blast = getBool(hContact, AIM_KEY_BLS, false); int res = aim_send_message(hServerConn, seqno, sn, msg, false, blast); mir_free(msg); mir_free(sn); if (!res || blast || 0 == getByte(AIM_KEY_DC, 1)) { msg_ack_param *msg_ack = (msg_ack_param*)mir_alloc(sizeof(msg_ack_param)); msg_ack->hContact = hContact; msg_ack->msg = NULL; msg_ack->id = res; msg_ack->success = res != 0; ForkThread(&CAimProto::msg_ack_success, msg_ack); } return res; } //////////////////////////////////////////////////////////////////////////////////////// // SendUrl int __cdecl CAimProto::SendUrl(HANDLE hContact, int flags, const char* url) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // SetApparentMode - sets the visibility m_iStatus int __cdecl CAimProto::SetApparentMode(HANDLE hContact, int mode) { return 0; } //////////////////////////////////////////////////////////////////////////////////////// // SetStatus - sets the protocol m_iStatus int __cdecl CAimProto::SetStatus(int iNewStatus) { switch (iNewStatus) { case ID_STATUS_FREECHAT: iNewStatus = ID_STATUS_ONLINE; break; case ID_STATUS_DND: case ID_STATUS_OCCUPIED: case ID_STATUS_ONTHEPHONE: #ifdef ALLOW_BUSY iNewStatus = ID_STATUS_OCCUPIED; break; #endif case ID_STATUS_OUTTOLUNCH: case ID_STATUS_NA: iNewStatus = ID_STATUS_AWAY; break; } if (iNewStatus == m_iStatus) return 0; if (iNewStatus == ID_STATUS_OFFLINE) { broadcast_status(ID_STATUS_OFFLINE); return 0; } m_iDesiredStatus = iNewStatus; if (m_iStatus == ID_STATUS_OFFLINE) { broadcast_status(ID_STATUS_CONNECTING); ForkThread(&CAimProto::start_connection, (void*)iNewStatus); } else if (m_iStatus > ID_STATUS_OFFLINE) { switch(iNewStatus) { case ID_STATUS_ONLINE: aim_set_status(hServerConn, seqno, AIM_STATUS_ONLINE); broadcast_status(ID_STATUS_ONLINE); break; case ID_STATUS_INVISIBLE: aim_set_status(hServerConn, seqno, AIM_STATUS_INVISIBLE); broadcast_status(ID_STATUS_INVISIBLE); break; case ID_STATUS_OCCUPIED: aim_set_status(hServerConn, seqno, AIM_STATUS_BUSY | AIM_STATUS_AWAY); broadcast_status(ID_STATUS_OCCUPIED); break; case ID_STATUS_AWAY: aim_set_status(hServerConn, seqno, AIM_STATUS_AWAY); broadcast_status(ID_STATUS_AWAY); break; } } return 0; } //////////////////////////////////////////////////////////////////////////////////////// // GetAwayMsg - returns a contact's away message void __cdecl CAimProto::get_online_msg_thread(void* arg) { Sleep(150); const HANDLE hContact = arg; DBVARIANT dbv; if (!db_get_ts(hContact, MOD_KEY_CL, OTH_KEY_SM, &dbv)) { ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)dbv.ptszVal); db_free(&dbv); } else ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, 0); } HANDLE __cdecl CAimProto::GetAwayMsg(HANDLE hContact) { if (state != 1) return 0; int status = getWord(hContact, AIM_KEY_ST, ID_STATUS_OFFLINE); switch (status) { case ID_STATUS_AWAY: case ID_STATUS_ONLINE: ForkThread(&CAimProto::get_online_msg_thread, hContact); break; default: return 0; } return (HANDLE)1; } //////////////////////////////////////////////////////////////////////////////////////// // PSR_AWAYMSG int __cdecl CAimProto::RecvAwayMsg(HANDLE hContact, int statusMode, PROTORECVEVENT* pre) { ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)pre->szMessage); return 0; } //////////////////////////////////////////////////////////////////////////////////////// // PSS_AWAYMSG int __cdecl CAimProto::SendAwayMsg(HANDLE hContact, HANDLE hProcess, const char* msg) { return 1; } //////////////////////////////////////////////////////////////////////////////////////// // SetAwayMsg - sets the away m_iStatus message int __cdecl CAimProto::SetAwayMsg(int status, const TCHAR* msg) { char** msgptr = get_status_msg_loc(status); if (msgptr == NULL) return 1; char* nmsg = mir_utf8encodeT(msg); mir_free(*msgptr); *msgptr = nmsg; switch (status) { case ID_STATUS_FREECHAT: status = ID_STATUS_ONLINE; break; case ID_STATUS_DND: case ID_STATUS_OCCUPIED: case ID_STATUS_ONTHEPHONE: #ifdef ALLOW_BUSY status = ID_STATUS_OCCUPIED; break; #endif case ID_STATUS_OUTTOLUNCH: case ID_STATUS_NA: status = ID_STATUS_AWAY; break; } if (state == 1 && status == m_iStatus) { if (!_strcmps(last_status_msg, nmsg)) return 0; mir_free(last_status_msg); last_status_msg = mir_strdup(nmsg); aim_set_statusmsg(hServerConn, seqno, nmsg); aim_set_away(hServerConn, seqno, nmsg, status == ID_STATUS_AWAY || status == ID_STATUS_OCCUPIED); } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // UserIsTyping - sends a UTN notification int __cdecl CAimProto::UserIsTyping(HANDLE hContact, int type) { if (state != 1) return 0; if (getWord(hContact, AIM_KEY_ST, ID_STATUS_OFFLINE) == ID_STATUS_ONTHEPHONE) return 0; DBVARIANT dbv; if (!getBool(hContact, AIM_KEY_BLS, false) && !getString(hContact, AIM_KEY_SN, &dbv)) { if (type == PROTOTYPE_SELFTYPING_ON) aim_typing_notification(hServerConn, seqno, dbv.pszVal, 0x0002); else if (type == PROTOTYPE_SELFTYPING_OFF) aim_typing_notification(hServerConn, seqno, dbv.pszVal, 0x0000); db_free(&dbv); } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // OnEvent - maintain protocol events int __cdecl CAimProto::OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam) { switch (eventType) { case EV_PROTO_ONLOAD: return OnModulesLoaded(0, 0); case EV_PROTO_ONMENU: InitMainMenus(); break; case EV_PROTO_ONOPTIONS: return OnOptionsInit(wParam, lParam); case EV_PROTO_ONERASE: { char szDbsettings[64]; mir_snprintf(szDbsettings, sizeof(szDbsettings), "%sP2P", m_szModuleName); CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbsettings); } break; case EV_PROTO_ONRENAME: if (hMenuRoot) { CLISTMENUITEM clmi = { sizeof(clmi) }; clmi.flags = CMIM_NAME | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED; clmi.ptszName = m_tszUserName; Menu_ModifyItem(hMenuRoot, &clmi); } break; case EV_PROTO_ONCONTACTDELETED: return OnContactDeleted(wParam, lParam); case EV_PROTO_DBSETTINGSCHANGED: return OnDbSettingChanged(wParam, lParam); } return 1; }