diff options
-rwxr-xr-x | protocols/JabberG/jabber.vcxproj | 1 | ||||
-rwxr-xr-x | protocols/JabberG/jabber.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/JabberG/src/jabber_archive.cpp | 272 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_caps.cpp | 2 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_caps.h | 10 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_iqid.cpp | 4 | ||||
-rw-r--r-- | protocols/JabberG/src/jabber_menu.cpp | 4 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_opt.cpp | 1 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_proto.cpp | 1 | ||||
-rwxr-xr-x | protocols/JabberG/src/jabber_proto.h | 9 |
10 files changed, 302 insertions, 5 deletions
diff --git a/protocols/JabberG/jabber.vcxproj b/protocols/JabberG/jabber.vcxproj index 7eb80b6e17..81294626ef 100755 --- a/protocols/JabberG/jabber.vcxproj +++ b/protocols/JabberG/jabber.vcxproj @@ -30,6 +30,7 @@ <ClCompile Include="src\jabber_adhoc.cpp" />
<ClCompile Include="src\jabber_agent.cpp" />
<ClCompile Include="src\jabber_api.cpp" />
+ <ClCompile Include="src\jabber_archive.cpp" />
<ClCompile Include="src\jabber_bookmarks.cpp" />
<ClCompile Include="src\jabber_byte.cpp" />
<ClCompile Include="src\jabber_caps.cpp" />
diff --git a/protocols/JabberG/jabber.vcxproj.filters b/protocols/JabberG/jabber.vcxproj.filters index 3a72f63b4b..01919b4315 100755 --- a/protocols/JabberG/jabber.vcxproj.filters +++ b/protocols/JabberG/jabber.vcxproj.filters @@ -14,6 +14,9 @@ <ClCompile Include="src\jabber_api.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\jabber_archive.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="src\jabber_bookmarks.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/protocols/JabberG/src/jabber_archive.cpp b/protocols/JabberG/src/jabber_archive.cpp new file mode 100644 index 0000000000..2c478b2b61 --- /dev/null +++ b/protocols/JabberG/src/jabber_archive.cpp @@ -0,0 +1,272 @@ +/*
+
+Jabber Protocol Plugin for Miranda NG
+
+Copyright (c) 2002-04 Santithorn Bunchua
+Copyright (c) 2005-12 George Hazan
+Copyright (C) 2012-20 Miranda NG team
+
+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 "jabber_iq.h"
+#include "jabber_caps.h"
+
+bool operator==(const DBEVENTINFO &ev1, const DBEVENTINFO &ev2)
+{
+ return ev1.timestamp == ev2.timestamp && ev1.eventType == ev2.eventType && ev1.cbBlob == ev2.cbBlob && (ev1.flags & DBEF_SENT) == (ev2.flags & DBEF_SENT);
+}
+
+void CJabberProto::EnableArchive(bool bEnable)
+{
+ m_ThreadInfo->send(XmlNodeIq("set", SerialNext())
+ << XCHILDNS("auto", JABBER_FEAT_ARCHIVE) << XATTR("save", (bEnable) ? "true" : "false"));
+}
+
+void CJabberProto::RetrieveMessageArchive(MCONTACT hContact, JABBER_LIST_ITEM *pItem)
+{
+ if (pItem->bHistoryRead)
+ return;
+
+ pItem->bHistoryRead = true;
+
+ XmlNodeIq iq(AddIQ(&CJabberProto::OnIqResultGetCollectionList, JABBER_IQ_TYPE_GET));
+ TiXmlElement *list = iq << XCHILDNS("list", JABBER_FEAT_ARCHIVE) << XATTR("with", pItem->jid);
+
+ time_t tmLast = getDword(hContact, "LastCollection", 0);
+ if (tmLast) {
+ char buf[40];
+ list << XATTR("start", time2str(tmLast, buf, _countof(buf)));
+ }
+ m_ThreadInfo->send(iq);
+}
+
+void CJabberProto::OnIqResultGetCollectionList(const TiXmlElement *iqNode, CJabberIqInfo*)
+{
+ const char *to = XmlGetAttr(iqNode, "to");
+ if (to == nullptr || mir_strcmp(XmlGetAttr(iqNode, "type"), "result"))
+ return;
+
+ auto *list = XmlFirstChild(iqNode, "list");
+ if (mir_strcmp(XmlGetAttr(list, "xmlns"), JABBER_FEAT_ARCHIVE))
+ return;
+
+ for (auto *itemNode : TiXmlFilter(list, "chat")) {
+ const char *start = XmlGetAttr(itemNode, "start");
+ const char *with = XmlGetAttr(itemNode, "with");
+ if (!start || !with)
+ continue;
+
+ m_ThreadInfo->send(XmlNodeIq(AddIQ(&CJabberProto::OnIqResultGetCollection, JABBER_IQ_TYPE_GET))
+ << XCHILDNS("retrieve", JABBER_FEAT_ARCHIVE) << XATTR("with", with) << XATTR("start", start));
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static DWORD dwPreviousTimeStamp = -1;
+static MCONTACT hPreviousContact = INVALID_CONTACT_ID;
+static MEVENT hPreviousDbEvent = 0;
+
+// Returns TRUE if the event already exist in the database
+bool IsDuplicateEvent(MCONTACT hContact, DBEVENTINFO& dbei)
+{
+ // get last event
+ MEVENT hExistingDbEvent = db_event_last(hContact);
+ if (!hExistingDbEvent)
+ return false;
+
+ DBEVENTINFO dbeiExisting = {};
+ db_event_get(hExistingDbEvent, &dbeiExisting);
+ DWORD dwEventTimeStamp = dbeiExisting.timestamp;
+
+ // compare with last timestamp
+ if (dbei.timestamp > dwEventTimeStamp) {
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dwEventTimeStamp;
+ return false;
+ }
+
+ if (hContact != hPreviousContact) {
+ hPreviousContact = hContact;
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dwEventTimeStamp;
+
+ // get first event
+ if (!(hExistingDbEvent = db_event_first(hContact)))
+ return false;
+
+ memset(&dbeiExisting, 0, sizeof(dbeiExisting));
+ db_event_get(hExistingDbEvent, &dbeiExisting);
+ dwEventTimeStamp = dbeiExisting.timestamp;
+
+ // compare with first timestamp
+ if (dbei.timestamp <= dwEventTimeStamp) {
+ // remember event
+ dwPreviousTimeStamp = dwEventTimeStamp;
+ hPreviousDbEvent = hExistingDbEvent;
+
+ if (dbei.timestamp != dwEventTimeStamp)
+ return false;
+ }
+ }
+
+ // check for equal timestamps
+ if (dbei.timestamp == dwPreviousTimeStamp) {
+ memset(&dbeiExisting, 0, sizeof(dbeiExisting));
+ db_event_get(hPreviousDbEvent, &dbeiExisting);
+
+ if (dbei == dbeiExisting)
+ return true;
+
+ // find event with another timestamp
+ hExistingDbEvent = db_event_next(hContact, hPreviousDbEvent);
+ while (hExistingDbEvent != 0) {
+ memset(&dbeiExisting, 0, sizeof(dbeiExisting));
+ db_event_get(hExistingDbEvent, &dbeiExisting);
+
+ if (dbeiExisting.timestamp != dwPreviousTimeStamp) {
+ // use found event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dbeiExisting.timestamp;
+ break;
+ }
+
+ hPreviousDbEvent = hExistingDbEvent;
+ hExistingDbEvent = db_event_next(hContact, hExistingDbEvent);
+ }
+ }
+
+ hExistingDbEvent = hPreviousDbEvent;
+
+ if (dbei.timestamp <= dwPreviousTimeStamp) {
+ // look back
+ while (hExistingDbEvent != 0) {
+ memset(&dbeiExisting, 0, sizeof(dbeiExisting));
+ db_event_get(hExistingDbEvent, &dbeiExisting);
+
+ if (dbei.timestamp > dbeiExisting.timestamp) {
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dbeiExisting.timestamp;
+ return false;
+ }
+
+ // Compare event with import candidate
+ if (dbei == dbeiExisting) {
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dbeiExisting.timestamp;
+ return true;
+ }
+
+ // Get previous event in chain
+ hExistingDbEvent = db_event_prev(hContact, hExistingDbEvent);
+ }
+ }
+ else {
+ // look forward
+ while (hExistingDbEvent != 0) {
+ memset(&dbeiExisting, 0, sizeof(dbeiExisting));
+ db_event_get(hExistingDbEvent, &dbeiExisting);
+
+ if (dbei.timestamp < dbeiExisting.timestamp) {
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dbeiExisting.timestamp;
+ return false;
+ }
+
+ // Compare event with import candidate
+ if (dbei == dbeiExisting) {
+ // remember event
+ hPreviousDbEvent = hExistingDbEvent;
+ dwPreviousTimeStamp = dbeiExisting.timestamp;
+ return true;
+ }
+
+ // Get next event in chain
+ hExistingDbEvent = db_event_next(hContact, hExistingDbEvent);
+ }
+ }
+ // reset last event
+ hPreviousContact = INVALID_CONTACT_ID;
+ return false;
+}
+
+void CJabberProto::OnIqResultGetCollection(const TiXmlElement *iqNode, CJabberIqInfo*)
+{
+ if (mir_strcmp(XmlGetAttr(iqNode, "type"), "result"))
+ return;
+
+ auto *chatNode = XmlFirstChild(iqNode, "chat");
+ if (!chatNode || mir_strcmp(XmlGetAttr(chatNode, "xmlns"), JABBER_FEAT_ARCHIVE))
+ return;
+
+ const char* start = XmlGetAttr(chatNode, "start");
+ const char* with = XmlGetAttr(chatNode, "with");
+ if (!start || !with)
+ return;
+
+ _tzset();
+
+ MCONTACT hContact = HContactFromJID(with);
+ time_t tmStart = str2time(start);
+ if (hContact == 0 || tmStart == 0)
+ return;
+
+ time_t tmLast = getDword(hContact, "LastCollection", 0);
+
+ for (auto *itemNode : TiXmlEnum(chatNode)) {
+ int from;
+ const char *itemName = itemNode->Name();
+ if (!mir_strcmp(itemName, "to"))
+ from = DBEF_SENT;
+ else if (!mir_strcmp(itemName, "from"))
+ from = 0;
+ else
+ continue;
+
+ const TiXmlElement *body = XmlFirstChild(itemNode, "body");
+ if (!body)
+ continue;
+
+ const char *tszBody = body->GetText();
+ const char *tszSecs = XmlGetAttr(itemNode, "secs");
+ if (!tszBody || !tszSecs)
+ continue;
+
+ DBEVENTINFO dbei = {};
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.szModule = m_szModuleName;
+ dbei.cbBlob = (DWORD)mir_strlen(tszBody) + 1;
+ dbei.flags = DBEF_READ + DBEF_UTF + from;
+ dbei.pBlob = (BYTE*)tszBody;
+ dbei.timestamp = tmStart + atol(tszSecs);
+ if (!IsDuplicateEvent(hContact, dbei))
+ db_event_add(hContact, &dbei);
+
+ tmStart = dbei.timestamp;
+ if (dbei.timestamp > tmLast)
+ tmLast = dbei.timestamp;
+ }
+
+ if (tmLast != 0)
+ setDword(hContact, "LastCollection", tmLast + 1);
+}
diff --git a/protocols/JabberG/src/jabber_caps.cpp b/protocols/JabberG/src/jabber_caps.cpp index bce11fb61f..6d2d6c7095 100755 --- a/protocols/JabberG/src/jabber_caps.cpp +++ b/protocols/JabberG/src/jabber_caps.cpp @@ -69,6 +69,8 @@ const JabberFeatCapPair g_JabberFeatCapPairs[] = { JABBER_FEAT_PRIVATE_STORAGE, JABBER_CAPS_PRIVATE_STORAGE, LPGEN("Supports private XML Storage (for bookmarks and other)") },
{ JABBER_FEAT_ATTENTION, JABBER_CAPS_ATTENTION, LPGEN("Supports attention requests ('nudge')") },
{ JABBER_FEAT_MAM, JABBER_CAPS_MAM, LPGEN("Support Message Archive Management (XEP-0313)") },
+ { JABBER_FEAT_ARCHIVE_AUTO, JABBER_CAPS_ARCHIVE_AUTO, LPGEN("Supports chat history retrieving") },
+ { JABBER_FEAT_ARCHIVE_MANAGE, JABBER_CAPS_ARCHIVE_MANAGE, LPGEN("Supports chat history management") },
{ JABBER_FEAT_USER_ACTIVITY, JABBER_CAPS_USER_ACTIVITY, LPGEN("Can report information about user activity") },
{ JABBER_FEAT_USER_ACTIVITY_NOTIFY, JABBER_CAPS_USER_ACTIVITY_NOTIFY, LPGEN("Receives information about user activity") },
{ JABBER_FEAT_MIRANDA_NOTES, JABBER_CAPS_MIRANDA_NOTES, LPGEN("Supports Miranda NG notes extension") },
diff --git a/protocols/JabberG/src/jabber_caps.h b/protocols/JabberG/src/jabber_caps.h index 980ff36777..34343fe5b0 100755 --- a/protocols/JabberG/src/jabber_caps.h +++ b/protocols/JabberG/src/jabber_caps.h @@ -102,6 +102,9 @@ typedef unsigned __int64 JabberCapsBits; #define JABBER_FEAT_VCARD_TEMP "vcard-temp"
#define JABBER_CAPS_VCARD_TEMP ((JabberCapsBits)1<<18)
+#define JABBER_FEAT_MAM "urn:xmpp:mam:2"
+#define JABBER_CAPS_MAM ((JabberCapsBits)1<<19)
+
#define JABBER_FEAT_XHTML "http://jabber.org/protocol/xhtml-im"
#define JABBER_CAPS_XHTML ((JabberCapsBits)1<<20)
@@ -144,8 +147,11 @@ typedef unsigned __int64 JabberCapsBits; #define JABBER_FEAT_PRIVATE_STORAGE "jabber:iq:private"
#define JABBER_CAPS_PRIVATE_STORAGE ((JabberCapsBits)1<<33)
-#define JABBER_FEAT_MAM "urn:xmpp:mam:2"
-#define JABBER_CAPS_MAM ((JabberCapsBits)1<<34)
+#define JABBER_FEAT_ARCHIVE_AUTO "urn:xmpp:archive:auto"
+#define JABBER_CAPS_ARCHIVE_AUTO ((JabberCapsBits)1<<34)
+
+#define JABBER_FEAT_ARCHIVE_MANAGE "urn:xmpp:archive:manage"
+#define JABBER_CAPS_ARCHIVE_MANAGE ((JabberCapsBits)1<<35)
#define JABBER_FEAT_ATTENTION "urn:xmpp:attention:0"
#define JABBER_CAPS_ATTENTION ((JabberCapsBits)1<<36)
diff --git a/protocols/JabberG/src/jabber_iqid.cpp b/protocols/JabberG/src/jabber_iqid.cpp index 690e936ad8..c6e0432206 100755 --- a/protocols/JabberG/src/jabber_iqid.cpp +++ b/protocols/JabberG/src/jabber_iqid.cpp @@ -123,9 +123,11 @@ void CJabberProto::OnProcessLoginRq(ThreadData *info, DWORD rq) DWORD dwMask = JABBER_LOGIN_ROSTER | JABBER_LOGIN_BOOKMARKS | JABBER_LOGIN_SERVERINFO;
if ((info->dwLoginRqs & dwMask) == dwMask && !(info->dwLoginRqs & JABBER_LOGIN_BOOKMARKS_AJ)) {
+ if (info->jabberServerCaps & JABBER_CAPS_ARCHIVE_AUTO)
+ EnableArchive(m_bEnableMsgArchive != 0);
- // Server seems to support carbon copies, let's enable/disable them
if (info->jabberServerCaps & JABBER_CAPS_CARBONS)
+ // Server seems to support carbon copies, let's enable/disable them
m_ThreadInfo->send(XmlNodeIq("set", SerialNext()) << XCHILDNS((m_bEnableCarbons) ? "enable" : "disable", JABBER_FEAT_CARBONS));
// Server seems to support MAM, let's retrieve MAM settings
diff --git a/protocols/JabberG/src/jabber_menu.cpp b/protocols/JabberG/src/jabber_menu.cpp index 92acc5ca6d..6bb8ec5444 100644 --- a/protocols/JabberG/src/jabber_menu.cpp +++ b/protocols/JabberG/src/jabber_menu.cpp @@ -803,8 +803,8 @@ int CJabberProto::OnProcessSrmmEvent(WPARAM, LPARAM lParam) ptrA jid(getUStringA(event->hContact, "jid"));
if (jid != nullptr) {
JABBER_LIST_ITEM *pItem = ListGetItemPtr(LIST_ROSTER, jid);
- // if (pItem)
- // RetrieveMessageArchive(event->hContact, pItem);
+ if (pItem && m_ThreadInfo && (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_ARCHIVE_AUTO) && m_bEnableMsgArchive)
+ RetrieveMessageArchive(event->hContact, pItem);
}
}
else if (event->uType == MSG_WINDOW_EVT_CLOSING) {
diff --git a/protocols/JabberG/src/jabber_opt.cpp b/protocols/JabberG/src/jabber_opt.cpp index acc94be5fd..4a273b5168 100755 --- a/protocols/JabberG/src/jabber_opt.cpp +++ b/protocols/JabberG/src/jabber_opt.cpp @@ -782,6 +782,7 @@ public: m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Automatically save received notes"), m_proto->m_bAutosaveNotes);
m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Inline pictures in messages (XEP-0231)"), m_proto->m_bInlinePictures);
m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Enable chat states sending (XEP-0085)"), m_proto->m_bEnableChatStates);
+ m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Enable server-side history (XEP-0136)"), m_proto->m_bEnableMsgArchive);
m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Enable carbon copies (XEP-0280)"), m_proto->m_bEnableCarbons);
m_otvOptions.AddOption(LPGENW("Messaging") L"/" LPGENW("Use Stream Management (XEP-0198) if possible (Testing)"), m_proto->m_bEnableStreamMgmt);
diff --git a/protocols/JabberG/src/jabber_proto.cpp b/protocols/JabberG/src/jabber_proto.cpp index de68283c16..ad3626164f 100755 --- a/protocols/JabberG/src/jabber_proto.cpp +++ b/protocols/JabberG/src/jabber_proto.cpp @@ -93,6 +93,7 @@ CJabberProto::CJabberProto(const char *aProtoName, const wchar_t *aUserName) : m_bEnableAvatars(this, "EnableAvatars", true),
m_bEnableCarbons(this, "EnableCarbons", true),
m_bEnableChatStates(this, "EnableChatStates", true),
+ m_bEnableMsgArchive(this, "EnableMsgArchive", false),
m_bEnableRemoteControl(this, "EnableRemoteControl", false),
m_bEnableStreamMgmt(this, "UseStreamMgmt", false),
m_bEnableUserActivity(this, "EnableUserActivity", true),
diff --git a/protocols/JabberG/src/jabber_proto.h b/protocols/JabberG/src/jabber_proto.h index b6a599c496..8539ec23f6 100755 --- a/protocols/JabberG/src/jabber_proto.h +++ b/protocols/JabberG/src/jabber_proto.h @@ -188,6 +188,7 @@ struct CJabberProto : public PROTO<CJabberProto>, public IJabberInterface CMOption<bool> m_bEnableAvatars;
CMOption<bool> m_bEnableCarbons;
CMOption<bool> m_bEnableChatStates;
+ CMOption<bool> m_bEnableMsgArchive;
CMOption<bool> m_bEnableRemoteControl;
CMOption<bool> m_bEnableStreamMgmt;
CMOption<bool> m_bEnableUserActivity;
@@ -359,6 +360,14 @@ struct CJabberProto : public PROTO<CJabberProto>, public IJabberInterface void ContactMenuAdhocCommands(struct CJabberAdhocStartupParams *param);
+ //---- jabber_archive.c --------------------------------------------------------------
+
+ void EnableArchive(bool bEnable);
+ void RetrieveMessageArchive(MCONTACT hContact, JABBER_LIST_ITEM *pItem);
+
+ void OnIqResultGetCollection(const TiXmlElement *iqNode, CJabberIqInfo*);
+ void OnIqResultGetCollectionList(const TiXmlElement *iqNode, CJabberIqInfo*);
+
//---- jabber_bookmarks.c ------------------------------------------------------------
INT_PTR __cdecl OnMenuHandleBookmarks(WPARAM wParam, LPARAM lParam);
|