//*************************************************************************************** // // Google Extension plugin for the Miranda IM's Jabber protocol // Copyright (c) 2011 bems@jabber.org, George Hazan (ghazan@jabber.ru) // // 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, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // //*************************************************************************************** #include "StdAfx.h" #include "handlers.h" #include "db.h" #include "inbox.h" #include "notifications.h" #include "options.h" static const LPCTSTR JABBER_IQID = _T("mir_"); static const LPCTSTR JABBER_IQID_FORMAT = _T("mir_%d"); static const LPCTSTR NOTIFY_FEATURE_XMLNS = _T("google:mail:notify"); static const LPCTSTR SETTING_FEATURE_XMLNS = _T("google:setting"); static const LPCTSTR DISCOVERY_XMLNS = _T("http://jabber.org/protocol/disco#info"); static const LPCTSTR MESSAGE_URL_FORMAT_STANDARD = _T("%s/#inbox/%x%08x"); static const LPCTSTR MESSAGE_URL_FORMAT_HTML = _T("%s/h/?v=c&th=%x%08x"); static const LPCTSTR ATTRNAME_TYPE = _T("type"); static const LPCTSTR ATTRNAME_FROM = _T("from"); static const LPCTSTR ATTRNAME_TO = _T("to"); static const LPCTSTR ATTRNAME_URL = _T("url"); static const LPCTSTR ATTRNAME_TID = _T("tid"); static const LPCTSTR ATTRNAME_UNREAD = _T("unread"); static const LPCTSTR ATTRNAME_XMLNS = _T("xmlns"); static const LPCTSTR ATTRNAME_ID = _T("id"); static const LPCTSTR ATTRNAME_TOTAL_MATCHED = _T("total-matched"); static const LPCTSTR ATTRNAME_NAME = _T("name"); static const LPCTSTR ATTRNAME_ADDRESS = _T("address"); static const LPCTSTR ATTRNAME_RESULT_TIME = _T("result-time"); static const LPCTSTR ATTRNAME_NEWER_THAN_TIME = _T("newer-than-time"); static const LPCTSTR ATTRNAME_NEWER_THAN_TID = _T("newer-than-tid"); static const LPCTSTR ATTRNAME_VALUE = _T("value"); static const LPCTSTR ATTRNAME_VAR = _T("var"); static const LPCTSTR IQTYPE_RESULT = _T("result"); static const LPCTSTR IQTYPE_SET = _T("set"); static const LPCTSTR IQTYPE_GET = _T("get"); static const LPCTSTR NODENAME_MAILBOX = _T("mailbox"); static const LPCTSTR NODENAME_QUERY = _T("query"); static const LPCTSTR NODENAME_IQ = _T("iq"); static const LPCTSTR NODENAME_USERSETTING = _T("usersetting"); static const LPCTSTR NODENAME_MAILNOTIFICATIONS = _T("mailnotifications"); static const LPCTSTR NODENAME_SUBJECT = _T("subject"); static const LPCTSTR NODENAME_SNIPPET = _T("snippet"); static const LPCTSTR NODENAME_SENDERS = _T("senders"); static const LPCTSTR NODENAME_FEATURE = _T("feature"); static const LPCTSTR NODENAME_NEW_MAIL = _T("new-mail"); static const LPCTSTR SETTING_TRUE = _T("true"); static const DWORD RESPONSE_TIMEOUT = 1000 * 60 * 60; static const DWORD TIMER_INTERVAL = 1000 * 60 * 2; XML_API xi = {0}; #include void FormatMessageUrl(LPCTSTR format, LPTSTR buf, LPCTSTR mailbox, LPCTSTR tid) { ULARGE_INTEGER iTid; iTid.QuadPart = _tstoi64(tid); int l = lstrlen(buf); wsprintf(buf, format, mailbox, iTid.HighPart, iTid.LowPart); assert(l >= lstrlen(buf)); } void MakeUrlHex(LPTSTR url, LPCTSTR tid) { ULARGE_INTEGER iTid; iTid.QuadPart = _tstoi64(tid); LPTSTR tidInUrl = _tcsstr(url, tid); LPTSTR trail = tidInUrl + lstrlen(tid); wsprintf(tidInUrl, _T("%x%08x"), iTid.HighPart, iTid.LowPart); wmemmove(tidInUrl + lstrlen(tidInUrl), trail, lstrlen(trail) + 1); } LPTSTR ExtractJid(LPCTSTR jidWithRes) { LPCTSTR p = _tcsrchr(jidWithRes, '/'); if (p == NULL) return mir_tstrdup(jidWithRes); size_t l = size_t(p - jidWithRes); LPTSTR result = (LPTSTR)mir_alloc((l + 1) * sizeof(TCHAR)); _tcsncpy(result, jidWithRes, l); result[l] = 0; return result; } BOOL TimerHandler(IJabberInterface *ji, HXML node, void *pUserData); BOOL InternalListHandler(IJabberInterface *ji, HXML node, LPCTSTR jid, LPCTSTR mailboxUrl) { ULONGLONG maxTid = 0; LPCTSTR sMaxTid = NULL; int unreadCount = 0; for (int i = 0; i < xi.getChildCount(node); i++) { LPCTSTR sTid = xi.getAttrValue(xi.getChild(node, i), ATTRNAME_TID); ULONGLONG tid = _tcstoui64(sTid, NULL, 10); if (tid > maxTid) { maxTid = tid; sMaxTid = sTid; } HXML senders = xi.getChildByPath(xi.getChild(node, i), NODENAME_SENDERS, FALSE); for (int j = 0; j < xi.getChildCount(senders); j++) if (xi.getAttrValue(xi.getChild(senders, j), ATTRNAME_UNREAD)) { unreadCount++; break; } } LPCSTR acc = GetJidAcc(jid); if (!acc) return FALSE; if (!unreadCount) return TRUE; DWORD settings = ReadNotificationSettings(acc); if (unreadCount > 5) { CloseNotifications(acc, mailboxUrl, jid, FALSE); UnreadMailNotification(acc, jid, mailboxUrl, xi.getAttrValue(node, ATTRNAME_TOTAL_MATCHED)); } else for (int i = 0; i < xi.getChildCount(node); i++) { MAIL_THREAD_NOTIFICATION mtn = {0}; HXML thread = xi.getChild(node, i); mtn.subj = xi.getText(xi.getChildByPath(thread, NODENAME_SUBJECT, FALSE)); mtn.snip = xi.getText(xi.getChildByPath(thread, NODENAME_SNIPPET, FALSE)); int threadUnreadCount = 0; HXML senders = xi.getChildByPath(thread, NODENAME_SENDERS, FALSE); for (int j = 0; threadUnreadCount < SENDER_COUNT && j < xi.getChildCount(senders); j++) { HXML sender = xi.getChild(senders, j); if (xi.getAttrValue(sender, ATTRNAME_UNREAD)) { mtn.senders[threadUnreadCount].name = xi.getAttrValue(sender, ATTRNAME_NAME); mtn.senders[threadUnreadCount].addr = xi.getAttrValue(sender, ATTRNAME_ADDRESS); threadUnreadCount++; } } LPCTSTR url = xi.getAttrValue(thread, ATTRNAME_URL); LPCTSTR tid = xi.getAttrValue(thread, ATTRNAME_TID); if (ReadCheckbox(0, IDC_STANDARDVIEW, settings)) FormatMessageUrl(MESSAGE_URL_FORMAT_STANDARD, (LPTSTR)url, mailboxUrl, tid); else if (ReadCheckbox(0, IDC_HTMLVIEW, settings)) FormatMessageUrl(MESSAGE_URL_FORMAT_HTML, (LPTSTR)url, mailboxUrl, tid); else MakeUrlHex((LPTSTR)url, tid); CloseNotifications(acc, url, jid, i); UnreadThreadNotification(acc, jid, url, xi.getAttrValue(node, ATTRNAME_TOTAL_MATCHED), &mtn); } LPCTSTR time = xi.getAttrValue(node, ATTRNAME_RESULT_TIME); WriteJidSetting(LAST_MAIL_TIME_FROM_JID, jid, time); WriteJidSetting(LAST_THREAD_ID_FROM_JID, jid, sMaxTid); return TRUE; } BOOL MailListHandler(IJabberInterface *ji, HXML node, void *pUserData) { LPCTSTR jidWithRes = xi.getAttrValue(node, ATTRNAME_TO); __try { if (!node || lstrcmp(xi.getAttrValue(node, ATTRNAME_TYPE), IQTYPE_RESULT)) return TRUE; LPCTSTR jid = xi.getAttrValue(node, ATTRNAME_FROM); assert(jid); node = xi.getChildByPath(node, NODENAME_MAILBOX, FALSE); if (!node) return TRUE; // empty list LPCTSTR url = xi.getAttrValue(node, ATTRNAME_URL); return InternalListHandler(ji, node, jid, url); } __finally { if (jidWithRes) ji->Net()->AddTemporaryIqHandler(TimerHandler, JABBER_IQ_TYPE_RESULT, 0, (PVOID)_tcsdup(jidWithRes), TIMER_INTERVAL); // Never get a real result stanza. Results elapsed request after WAIT_TIMER_INTERVAL ms } } void RequestMail(LPCTSTR jidWithRes, IJabberInterface *ji) { HXML child = NULL; HXML node = xi.createNode(NODENAME_IQ, NULL, FALSE); xi.addAttr(node, ATTRNAME_TYPE, IQTYPE_GET); xi.addAttr(node, ATTRNAME_FROM, jidWithRes); UINT uID = ji->Net()->SerialNext(); mir_ptr jid( ExtractJid(jidWithRes)); xi.addAttr(node, ATTRNAME_TO, jid); mir_ptr lastMailTime( ReadJidSetting(LAST_MAIL_TIME_FROM_JID, jid)), lastThreadId( ReadJidSetting(LAST_THREAD_ID_FROM_JID, jid)); TCHAR id[30]; mir_sntprintf(id, SIZEOF(id), JABBER_IQID_FORMAT, uID); xi.addAttr(node, ATTRNAME_ID, id); child = xi.addChild(node, NODENAME_QUERY, NULL); xi.addAttr(child, ATTRNAME_XMLNS, NOTIFY_FEATURE_XMLNS); xi.addAttr(child, ATTRNAME_NEWER_THAN_TIME, lastMailTime); xi.addAttr(child, ATTRNAME_NEWER_THAN_TID, lastThreadId); IJabberNetInterface* piNet = ji->Net(); if ( piNet ) if (piNet->SendXmlNode(node)) piNet->AddTemporaryIqHandler(MailListHandler, JABBER_IQ_TYPE_RESULT, (int)uID, NULL, RESPONSE_TIMEOUT); if (child) xi.destroyNode(child); if (node) xi.destroyNode(node); } BOOL TimerHandler(IJabberInterface *ji, HXML node, void *pUserData) { RequestMail((LPCTSTR)pUserData, ji); free(pUserData); return FALSE; } BOOL NewMailHandler(IJabberInterface *ji, HXML node, void *pUserData) { HXML response = xi.createNode(NODENAME_IQ, NULL, FALSE); __try { xi.addAttr(response, ATTRNAME_TYPE, IQTYPE_RESULT); LPCTSTR attr = xi.getAttrValue(node, ATTRNAME_ID); if (!attr) return FALSE; xi.addAttr(response, ATTRNAME_ID, attr); attr = xi.getAttrValue(node, ATTRNAME_FROM); if (attr) xi.addAttr(response, ATTRNAME_TO, attr); attr = xi.getAttrValue(node, ATTRNAME_TO); if (!attr) return FALSE; xi.addAttr(response, ATTRNAME_FROM, attr); int bytesSent = ji->Net()->SendXmlNode(response); RequestMail(attr, ji); return bytesSent > 0; } __finally { xi.destroyNode(response); } } void SetNotificationSetting(LPCTSTR jidWithResource, IJabberInterface *ji) { HXML child = NULL; HXML node = xi.createNode(NODENAME_IQ, NULL, FALSE); xi.addAttr(node, ATTRNAME_TYPE, IQTYPE_SET); xi.addAttr(node, ATTRNAME_FROM, jidWithResource); mir_ptr jid( ExtractJid(jidWithResource)); xi.addAttr(node, ATTRNAME_TO, jid); TCHAR id[30]; mir_sntprintf(id, SIZEOF(id), JABBER_IQID_FORMAT, ji->Net()->SerialNext()); xi.addAttr(node, ATTRNAME_ID, id); child = xi.addChild(node, NODENAME_USERSETTING, NULL); xi.addAttr(child, ATTRNAME_XMLNS, SETTING_FEATURE_XMLNS); child = xi.addChild(child, NODENAME_MAILNOTIFICATIONS, NULL); xi.addAttr(child, ATTRNAME_VALUE, SETTING_TRUE); ji->Net()->SendXmlNode(node); if (child) xi.destroyNode(child); if (node) xi.destroyNode(node); } BOOL DiscoverHandler(IJabberInterface *ji, HXML node, void *pUserData) { if (!node) return FALSE; LPCTSTR jid = xi.getAttrValue(node, ATTRNAME_TO); assert(jid); node = xi.getChildByAttrValue(node, NODENAME_QUERY, ATTRNAME_XMLNS, DISCOVERY_XMLNS); HXML child = xi.getChildByAttrValue(node, NODENAME_FEATURE, ATTRNAME_VAR, SETTING_FEATURE_XMLNS); if (child) SetNotificationSetting(jid, ji); child = xi.getChildByAttrValue(node, NODENAME_FEATURE, ATTRNAME_VAR, NOTIFY_FEATURE_XMLNS); if (child) { ji->Net()->AddIqHandler(NewMailHandler, JABBER_IQ_TYPE_SET, NOTIFY_FEATURE_XMLNS, NODENAME_NEW_MAIL); RequestMail(jid, ji); } return FALSE; } extern DWORD itlsRecursion; BOOL SendHandler(IJabberInterface *ji, HXML node, void *pUserData) { HXML queryNode = xi.getChildByAttrValue(node, NODENAME_QUERY, ATTRNAME_XMLNS, DISCOVERY_XMLNS); if (!queryNode) return FALSE; if ( lstrcmp(xi.getName(node), NODENAME_IQ) || lstrcmp(xi.getAttrValue(node, ATTRNAME_TYPE), IQTYPE_GET)) return FALSE; if (TlsGetValue(itlsRecursion)) return FALSE; TlsSetValue(itlsRecursion, (PVOID)TRUE); UINT id = ji->Net()->SerialNext(); HXML newNode = xi.createNode(NODENAME_IQ, NULL, FALSE); xi.addAttr(newNode, ATTRNAME_TYPE, IQTYPE_GET); xi.addAttr(newNode, ATTRNAME_TO, xi.getAttrValue(node, ATTRNAME_TO)); TCHAR idAttr[30]; mir_sntprintf(idAttr, SIZEOF(idAttr), JABBER_IQID_FORMAT, id); xi.addAttr(newNode, ATTRNAME_ID, idAttr); xi.addAttr(xi.addChild(newNode, NODENAME_QUERY, NULL), ATTRNAME_XMLNS, DISCOVERY_XMLNS); ji->Net()->SendXmlNode(newNode); xi.destroyNode(newNode); ji->Net()->AddTemporaryIqHandler(DiscoverHandler, JABBER_IQ_TYPE_RESULT, id, NULL, RESPONSE_TIMEOUT); TlsSetValue(itlsRecursion, (PVOID)FALSE); return FALSE; } ///////////////////////////////////////////////////////////////////////////////////////// IJabberInterface* IsGoogleAccount(LPCSTR szModuleName) { IJabberInterface *japi = getJabberApi(szModuleName); if (!japi) return NULL; DBVARIANT dbv; if ( db_get_s(NULL, szModuleName, "ManualHost", &dbv)) return NULL; bool res = !strcmp(dbv.pszVal, "talk.google.com"); db_free(&dbv); return (res) ? japi : NULL; } int AccListChanged(WPARAM wParam, LPARAM lParam) { if (wParam == PRAC_ADDED) { IJabberInterface *ji = getJabberApi(((PROTOACCOUNT*)lParam)->szModuleName); if (ji) ji->Net()->AddSendHandler(SendHandler); } return 0; } int ModulesLoaded(WPARAM wParam, LPARAM lParam) { int count; PROTOACCOUNT **protos; ProtoEnumAccounts(&count, &protos); for (int i=0; i < count; i++) { IJabberInterface *ji = IsGoogleAccount(protos[i]->szModuleName); if (ji) ji->Net()->AddSendHandler(SendHandler); } HookEvent(ME_JABBER_MENUINIT, InitMenus); HookOptionsInitialization(); return 0; }