From a760046b5c1ea7ed0ed4f1f9697bbe319c54518b Mon Sep 17 00:00:00 2001 From: mightymop Date: Thu, 23 Dec 2021 18:15:41 +0100 Subject: [PATCH] Rebase to current master: add XEP-0333 (Chatmarker archiving) --- src/i18n/monitoring_i18n.properties | 1 + .../openfire/archive/ArchiveInterceptor.java | 20 +++++- .../openfire/archive/ChatMarker.java | 68 +++++++++++++++++++ .../openfire/archive/ConversationEvent.java | 12 ++++ .../openfire/archive/ConversationManager.java | 43 +++++++++++- .../openfire/archive/ConversationUtils.java | 61 +++++++++++++++-- .../archive/GroupConversationInterceptor.java | 20 +++++- src/web/archiving-settings.jsp | 9 ++- 8 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 src/java/org/jivesoftware/openfire/archive/ChatMarker.java diff --git a/src/i18n/monitoring_i18n.properties b/src/i18n/monitoring_i18n.properties index d9314f2e2..979c2ddae 100644 --- a/src/i18n/monitoring_i18n.properties +++ b/src/i18n/monitoring_i18n.properties @@ -101,6 +101,7 @@ archive.settings.logs.link.unsecure=End-users can access the logs (using an unen archive.settings.logs.link.secure=Log files are also available via an encrypted HTTPS address at: {0}. archive.settings.logs.public.enable=Public Group Chat Weblog archive.settings.logs.public.enable.description=Expose log files of public group chats through a web interface. +archive.settings.chatmarker=Archive chatmarkers (XEP-0333) archive.search.title = Search Archive archive.search.participants = Participant(s): diff --git a/src/java/org/jivesoftware/openfire/archive/ArchiveInterceptor.java b/src/java/org/jivesoftware/openfire/archive/ArchiveInterceptor.java index bc96035f6..162c7ac2a 100644 --- a/src/java/org/jivesoftware/openfire/archive/ArchiveInterceptor.java +++ b/src/java/org/jivesoftware/openfire/archive/ArchiveInterceptor.java @@ -63,7 +63,7 @@ public void interceptPacket(Packet packet, Session session, boolean incoming, bo // Ignore any messages that don't have a body so that we skip events. // Note: XHTML messages should always include a body so we should be ok. It's // possible that we may need special XHTML filtering in the future, however. - if (message.getBody() != null) { + if (message.getBody() != null||(message.getBody()==null&&this.conversationManager.isChatmarkerArchivingEnabled())) { // Only process messages that are between two users, group chat rooms, or gateways. if (conversationManager.isConversation(message)) { // Process this event in the senior cluster member or local JVM when not in a cluster @@ -74,11 +74,27 @@ public void interceptPacket(Packet packet, Session session, boolean incoming, bo JID sender = message.getFrom(); JID receiver = message.getTo(); ConversationEventsQueue eventsQueue = conversationManager.getConversationEventsQueue(); - eventsQueue.addChatEvent(conversationManager.getConversationKey(sender, receiver), + if (message.getBody()!=null) + { + eventsQueue.addChatEvent(conversationManager.getConversationKey(sender, receiver), ConversationEvent.chatMessageReceived(sender, receiver, conversationManager.isMessageArchivingEnabled() ? message.getBody() : null, conversationManager.isMessageArchivingEnabled() ? message.toXML() : null, new Date())); + } + else + { + String stanza = message.toXML(); + ChatMarker.TYPE markertype = ChatMarker.searchForXep0333(stanza); + + if (markertype!=ChatMarker.TYPE.NONE) + { + eventsQueue.addChatEvent(conversationManager.getConversationKey(sender, receiver), + ConversationEvent.chatmarkerMessageReceived(sender, receiver,markertype, + conversationManager.isMessageArchivingEnabled() ? message.toXML() : null, + new Date())); + } + } } } } diff --git a/src/java/org/jivesoftware/openfire/archive/ChatMarker.java b/src/java/org/jivesoftware/openfire/archive/ChatMarker.java new file mode 100644 index 000000000..b1b6eb2fd --- /dev/null +++ b/src/java/org/jivesoftware/openfire/archive/ChatMarker.java @@ -0,0 +1,68 @@ +package org.jivesoftware.openfire.archive; + +public class ChatMarker { + + public enum TYPE + { + NONE, + MARKABLE, + RECEIVED, + DISPLAYED, + ACKNOWLEGED; + } + + public static TYPE searchForXep0333(String stanza) + { + if (stanza==null) + { + return TYPE.NONE; + } + + int idxmarkable = stanza.indexOf("",idxmarkable); + if (idxEnd==-1) + { + idxEnd = stanza.indexOf("",idxmarkable); + } + return idxEnd!=-1?(stanza.substring(idxmarkable, idxEnd).contains("urn:xmpp:chat-markers:0")?TYPE.MARKABLE:TYPE.NONE):TYPE.NONE; + } + else + if (idxreceived!=-1) + { + int idxEnd = stanza.indexOf("/>",idxreceived); + if (idxEnd==-1) + { + idxEnd = stanza.indexOf("",idxmarkable); + } + return idxEnd!=-1?(stanza.substring(idxreceived, idxEnd).contains("urn:xmpp:chat-markers:0")?TYPE.RECEIVED:TYPE.NONE):TYPE.NONE; + } + else + if (idxdisplayed!=-1) + { + int idxEnd = stanza.indexOf("/>",idxdisplayed); + if (idxEnd==-1) + { + idxEnd = stanza.indexOf("",idxmarkable); + } + return idxEnd!=-1?(stanza.substring(idxdisplayed, idxEnd).contains("urn:xmpp:chat-markers:0")?TYPE.DISPLAYED:TYPE.NONE):TYPE.NONE; + } + else + if (idxacknowled!=-1) + { + int idxEnd = stanza.indexOf("/>",idxacknowled); + if (idxEnd==-1) + { + idxEnd = stanza.indexOf("",idxmarkable); + } + return idxEnd!=-1?(stanza.substring(idxacknowled, idxEnd).contains("urn:xmpp:chat-markers:0")?TYPE.ACKNOWLEGED:TYPE.NONE):TYPE.NONE; + } + else + return TYPE.NONE; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/openfire/archive/ConversationEvent.java b/src/java/org/jivesoftware/openfire/archive/ConversationEvent.java index 4da25e181..44f56889e 100644 --- a/src/java/org/jivesoftware/openfire/archive/ConversationEvent.java +++ b/src/java/org/jivesoftware/openfire/archive/ConversationEvent.java @@ -60,6 +60,8 @@ public class ConversationEvent { private String nickname; + private ChatMarker.TYPE marker; + /** * Do not use this constructor. It only exists for serialization purposes. */ @@ -195,4 +197,14 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(type, date, body, stanza, sender, receiver, roomJID, user, nickname); } + + public static ConversationEvent chatmarkerMessageReceived(JID sender, JID receiver, ChatMarker.TYPE marker, String stanza, Date date) { + ConversationEvent event = new ConversationEvent(); + event.type = Type.chatMessageReceived; + event.sender = sender; + event.receiver = receiver; + event.marker = marker; + event.date = date; + return event; + } } diff --git a/src/java/org/jivesoftware/openfire/archive/ConversationManager.java b/src/java/org/jivesoftware/openfire/archive/ConversationManager.java index 44bb260b8..6468752e9 100644 --- a/src/java/org/jivesoftware/openfire/archive/ConversationManager.java +++ b/src/java/org/jivesoftware/openfire/archive/ConversationManager.java @@ -100,6 +100,7 @@ public class ConversationManager implements ComponentEventListener{ * Flag that indicates if messages of one-to-one chats should be archived. */ private boolean messageArchivingEnabled; + private boolean chatmarkerArchivingEnabled; /** * Flag that indicates if messages of group chats (in MUC rooms) should be archived. */ @@ -137,6 +138,13 @@ public class ConversationManager implements ComponentEventListener{ .setPlugin(MonitoringConstants.PLUGIN_NAME) .build(); + public static SystemProperty MESSAGEMARKER_ARCHIVING_ENABLED = SystemProperty.Builder.ofType(Boolean.class) + .setKey("conversation.chatmarkerArchiving") + .setDefaultValue(false) + .setDynamic(true) + .setPlugin(MonitoringConstants.PLUGIN_NAME) + .build(); + public static SystemProperty MESSAGE_ARCHIVING_ENABLED = SystemProperty.Builder.ofType(Boolean.class) .setKey("conversation.messageArchiving") .setDefaultValue(false) @@ -207,6 +215,7 @@ public ConversationManager(TaskEngine taskEngine) { public void start() { metadataArchivingEnabled = METADATA_ARCHIVING_ENABLED.getValue(); messageArchivingEnabled = MESSAGE_ARCHIVING_ENABLED.getValue(); + chatmarkerArchivingEnabled = MESSAGEMARKER_ARCHIVING_ENABLED.getValue(); if (messageArchivingEnabled && !metadataArchivingEnabled) { Log.warn("Metadata archiving must be enabled when message archiving is enabled. Overriding setting."); metadataArchivingEnabled = true; @@ -391,6 +400,15 @@ public boolean isArchivingEnabled() { return isMessageArchivingEnabled() || isRoomArchivingEnabled(); } + /** + * Returns true if chatmarker archiving is enabled, otherwise false + * + * @return + */ + public boolean isChatmarkerArchivingEnabled() { + return chatmarkerArchivingEnabled; + } + /** * Returns true if message archiving is enabled for one-to-one chats. When enabled, all messages in one-to-one conversations are stored in the * database. Note: it's not possible for meta-data archiving to be disabled when message archiving is enabled; enabling message archiving @@ -418,6 +436,17 @@ public void setMessageArchivingEnabled(boolean enabled) { } } + /** + * Sets whether message chatmarker archiving is enabled + * + * @param enabled + * true if chatmarker should be archived. + */ + public void setChatmarkerArchivingEnabled(boolean enabled) { + this.messageArchivingEnabled = enabled; + MESSAGEMARKER_ARCHIVING_ENABLED.setValue(enabled); + } + /** * Returns true if message archiving is enabled for group chats. When enabled, all messages in group conversations are stored in the database * unless a list of rooms was specified in {@link #getRoomsArchived()} . Note: it's not possible for meta-data archiving to be disabled when room @@ -737,6 +766,7 @@ void processMessage(JID sender, JID receiver, String body, String stanza, Date d synchronized (conversationKey.intern()) { Conversation conversation = conversations.get(conversationKey); // Create a new conversation if necessary. + ChatMarker.TYPE chatmarker=ChatMarker.searchForXep0333(stanza); if (conversation == null) { Collection participants = new ArrayList<>(2); participants.add(sender); @@ -784,7 +814,7 @@ else if ((date.getTime() - conversation.getLastActivity().getTime() > idleTime.t conversationArchiver.archive(conversation); } if (messageArchivingEnabled) { - if (body != null) { + if (body != null||(body==null&&chatmarkerArchivingEnabled&&chatmarker!=ChatMarker.TYPE.NONE)) { /* OF-677 - Workaround to prevent null messages being archived */ messageArchiver.archive(new ArchivedMessage(conversation.getConversationID(), sender, receiver, date, body, stanza, false, null) ); } @@ -819,6 +849,7 @@ void processRoomMessage(JID roomJID, JID sender, JID receiverIfPM, String nickna synchronized (conversationKey.intern()) { Conversation conversation = conversations.get(conversationKey); // Create a new conversation if necessary. + ChatMarker.TYPE chatmarker=ChatMarker.searchForXep0333(stanza); if (conversation == null) { // Make sure that the user joined the conversation before a message was received Date start = new Date(date.getTime() - 1); @@ -850,7 +881,7 @@ else if ((date.getTime() - conversation.getLastActivity().getTime() > idleTime.t } if (roomArchivingEnabled && (roomsArchived.isEmpty() || roomsArchived.contains(roomJID.getNode()))) { JID jid = new JID(roomJID + "/" + nickname); - if (body != null) { + if (body != null||(body==null&&chatmarkerArchivingEnabled&&chatmarker!=ChatMarker.TYPE.NONE)) { /* OF-677 - Workaround to prevent null messages being archived */ messageArchiver.archive( new ArchivedMessage(conversation.getConversationID(), sender, jid, date, body, roomArchivingStanzasEnabled ? stanza : "", false, receiverIfPM)); } @@ -1503,6 +1534,10 @@ public void propertySet(String property, Map params) { maxTime = DEFAULT_MAX_TIME; } } + else if (property.equals("conversation.chatmarkerArchiving")) { + String value = (String) params.get("value"); + chatmarkerArchivingEnabled = Boolean.valueOf(value); + } } public void propertyDeleted(String property, Map params) { @@ -1524,9 +1559,11 @@ public void propertyDeleted(String property, Map params) { setMaxAge(MAX_AGE.getDefaultValue()); } else if (property.equals("conversation.maxRetrievable")) { setMaxRetrievable(MAX_RETRIEVABLE.getDefaultValue()); - } else if (property.equals("conversation.maxTimeDebug")) { + } else if (property.equals("conversation.maxTimeDebug")) { Log.info("Monitoring plugin max time reset back to " + DEFAULT_MAX_TIME + " minutes"); setMaxTime(MAX_TIME.getDefaultValue()); + } else if (property.equals("conversation.chatmarkerArchiving")) { + setChatmarkerArchivingEnabled(MESSAGEMARKER_ARCHIVING_ENABLED.getDefaultValue()); } } diff --git a/src/java/org/jivesoftware/openfire/archive/ConversationUtils.java b/src/java/org/jivesoftware/openfire/archive/ConversationUtils.java index c9548985c..6e0b46f3b 100644 --- a/src/java/org/jivesoftware/openfire/archive/ConversationUtils.java +++ b/src/java/org/jivesoftware/openfire/archive/ConversationUtils.java @@ -216,10 +216,13 @@ private ByteArrayOutputStream buildPDFContent(ConversationManager conversationMa String prefix; if (!message.isRoomEvent()) { + /* + * If body is null, then it is a chatmarker, so we add the ressource to see which device has sent the marker. + * */ if (to == null) { - prefix = "[" + time + "] " + from + ": "; + prefix = "[" + time + "] " + from+(body==null?(" ("+message.getToJID().getResource()+")"):"")+ ": "; } else { - prefix = "[" + time + "] " + from + " -> " + to + ": "; + prefix = "[" + time + "] " + from+(body==null?(" ("+message.getToJID().getResource()+")"):"")+ " -> " + to + ": "; } Color color = colorMap.get(message.getFromJID()); if (color == null) { @@ -230,12 +233,35 @@ private ByteArrayOutputStream buildPDFContent(ConversationManager conversationMa } messageParagraph.add(new Text(prefix).setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD)).setFontColor(color)); - messageParagraph.add(new Text(body).setFontColor(ColorConstants.BLACK)); + messageParagraph.add(new Text(body==null?"":body).setFontColor(ColorConstants.BLACK)); } else { prefix = "[" + time + "] "; messageParagraph.add( new Text(prefix)).setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA_OBLIQUE)).setFontColor(ColorConstants.MAGENTA); - messageParagraph.add( new Text(body).setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA_OBLIQUE)).setFontColor(ColorConstants.MAGENTA)); + messageParagraph.add( new Text(body==null?"":body).setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA_OBLIQUE)).setFontColor(ColorConstants.MAGENTA)); + } + + if (body==null) + { + ChatMarker.TYPE chatmarker = ChatMarker.searchForXep0333(message.getStanza()); + switch (chatmarker) + { + case NONE: + messageParagraph.add("--unknown message--"); + break; + case MARKABLE: + messageParagraph.add("--message markable--"); + break; + case RECEIVED: + messageParagraph.add("--message received--"); + break; + case DISPLAYED: + messageParagraph.add("--message displayed--"); + break; + case ACKNOWLEGED: + messageParagraph.add("--message acknowleged--"); + break; + } } messageParagraph.add(new Text("\n")); } @@ -307,10 +333,35 @@ private ConversationInfo toConversationInfo(ConversationManager conversationMana if (conversation.getRoom() != null) { from = message.getToJID().getResource(); } - from = StringUtils.escapeHTMLTags(from); + /* + * If body is null, then it is a chatmarker, so we add the ressource to see which device has sent the marker. + * */ + from = StringUtils.escapeHTMLTags(from)+(message.getBody()==null?(" ("+message.getFromJID().getResource()+")"):""); to = to == null ? null : StringUtils.escapeHTMLTags(to); String cssLabel = cssLabels.get(message.getFromJID().toBareJID()); String body = StringUtils.escapeHTMLTags(message.getBody()); + if (body==null) + { + ChatMarker.TYPE chatmarker = ChatMarker.searchForXep0333(message.getStanza()); + switch (chatmarker) + { + case NONE: + body="--unknown message--"; + break; + case MARKABLE: + body="--message markable--"; + break; + case RECEIVED: + body="--message received--"; + break; + case DISPLAYED: + body="--message displayed--"; + break; + case ACKNOWLEGED: + body="--message acknowleged--"; + break; + } + } builder.append(""); if (!message.isRoomEvent()) { builder.append("").append("[").append(time).append("]").append(""); diff --git a/src/java/org/jivesoftware/openfire/archive/GroupConversationInterceptor.java b/src/java/org/jivesoftware/openfire/archive/GroupConversationInterceptor.java index 6d4e5e16d..b5bfb42da 100644 --- a/src/java/org/jivesoftware/openfire/archive/GroupConversationInterceptor.java +++ b/src/java/org/jivesoftware/openfire/archive/GroupConversationInterceptor.java @@ -148,9 +148,25 @@ public void messageReceived(JID roomJID, JID user, String nickname, Message mess conversationManager.getRoomsArchived().isEmpty() || conversationManager.getRoomsArchived().contains(roomJID.getNode())); - ConversationEventsQueue eventsQueue = conversationManager.getConversationEventsQueue(); - eventsQueue.addGroupChatEvent(conversationManager.getRoomConversationKey(roomJID), + if (withBody) + { + ConversationEventsQueue eventsQueue = conversationManager.getConversationEventsQueue(); + eventsQueue.addGroupChatEvent(conversationManager.getRoomConversationKey(roomJID), ConversationEvent.roomMessageReceived(roomJID, user, null, nickname, withBody ? message.getBody() : null, message.toXML(), now)); + } + else + { + String stanza = message.toXML(); + ChatMarker.TYPE markertype = ChatMarker.searchForXep0333(stanza); + + if (markertype!=ChatMarker.TYPE.NONE) + { + ConversationEventsQueue eventsQueue = conversationManager.getConversationEventsQueue(); + eventsQueue.addGroupChatEvent(conversationManager.getRoomConversationKey(roomJID), + ConversationEvent.chatmarkerMessageReceived(roomJID, user, markertype,stanza, + new Date())); + } + } } } diff --git a/src/web/archiving-settings.jsp b/src/web/archiving-settings.jsp index f062d57e8..6878b0d3a 100644 --- a/src/web/archiving-settings.jsp +++ b/src/web/archiving-settings.jsp @@ -184,6 +184,7 @@ boolean messageArchiving = conversationManager.isMessageArchivingEnabled(); boolean roomArchiving = conversationManager.isRoomArchivingEnabled(); boolean roomArchivingStanzas = conversationManager.isRoomArchivingStanzasEnabled(); + boolean chatmarkerArchiving = conversationManager.isChatmarkerArchivingEnabled(); Duration idleTime = Duration.ofMinutes(ParamUtils.getLongParameter(request, "idleTime", conversationManager.getIdleTime().toMinutes())); Duration maxTime = Duration.ofMinutes(ParamUtils.getLongParameter(request, "maxTime", conversationManager.getMaxTime().toMinutes())); @@ -238,6 +239,7 @@ roomArchiving = request.getParameter("roomArchiving") != null; roomArchivingStanzas = request.getParameter("roomArchivingStanzas") != null; String roomsArchived = request.getParameter("roomsArchived"); + chatmarkerArchiving = request.getParameter("chatmarkerArchiving") != null; // Validate params if (idleTime.toMinutes() < 1) { @@ -269,13 +271,14 @@ conversationManager.setRoomsArchived(StringUtils.stringToCollection(roomsArchived)); conversationManager.setIdleTime(idleTime); conversationManager.setMaxTime(maxTime); - + conversationManager.setChatmarkerArchivingEnabled(chatmarkerArchiving); conversationManager.setMaxAge(maxAge); conversationManager.setMaxRetrievable(maxRetrievable); webManager.logEvent("Changed archive settings (monitoring plugin)", "Metadata Archiving Enabled: " + metadataArchiving + ", Message Archiving Enabled: " + messageArchiving + + ", Chatmarker Archiving Enabled: " + chatmarkerArchiving + ", Room Archiving Enabled: " + roomArchiving + ", Room Archiving Stanzas Enabled: " + roomArchivingStanzas + ", RoomsArchived: " + StringUtils.stringToCollection(roomsArchived) @@ -360,6 +363,10 @@ /> + + + /> + />