From 3bc24088724448f1bc491130edb87372586b5a73 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 7 Dec 2018 20:21:01 +0100 Subject: some very basic MIX code (join/leave) that reutilizes MUC UI --- .../siacs/conversations/entities/MucOptions.java | 13 +++ .../conversations/generator/AbstractGenerator.java | 3 +- .../siacs/conversations/parser/AbstractParser.java | 14 ++++ .../siacs/conversations/parser/MessageParser.java | 89 ++++++++++++++++----- .../services/XmppConnectionService.java | 93 +++++++++++++++++++++- .../java/eu/siacs/conversations/xml/Namespace.java | 2 + .../siacs/conversations/xmpp/XmppConnection.java | 17 +++- 7 files changed, 207 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 2a751d95e..da840c145 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; @@ -111,6 +112,10 @@ public class MucOptions { return MessageArchiveService.Version.has(getFeatures()); } + public boolean isMix() { + return hasFeature(Namespace.MIX_CORE); + } + public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) { this.serviceDiscoveryResult = serviceDiscoveryResult; String name; @@ -716,6 +721,10 @@ public class MucOptions { this.role = Role.of(role); } + public void setRole(Role role) { + this.role = role; + } + public Affiliation getAffiliation() { return this.affiliation; } @@ -724,6 +733,10 @@ public class MucOptions { this.affiliation = Affiliation.of(affiliation); } + public void setAffiliation(Affiliation affiliation) { + this.affiliation = affiliation; + } + public long getPgpKeyId() { if (this.pgpKeyId != 0) { return this.pgpKeyId; diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index d320168bb..5833c198c 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -39,7 +39,8 @@ public abstract class AbstractGenerator { Namespace.BOOKMARKS+"+notify", "urn:xmpp:ping", "jabber:iq:version", - "http://jabber.org/protocol/chatstates" + "http://jabber.org/protocol/chatstates", + Namespace.MIX_CORE }; private final String[] MESSAGE_CONFIRMATION_FEATURES = { "urn:xmpp:chat-markers:0", diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 05297d74d..4598bd0f7 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -5,12 +5,14 @@ import java.text.SimpleDateFormat; import java.util.Locale; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; import rocks.xmpp.addr.Jid; @@ -97,6 +99,18 @@ public abstract class AbstractParser { return item.findChildContent("data", "urn:xmpp:avatar:data"); } + public static MucOptions.User parseParticipant(Conversation conversation, Element item) { + Element participant = item.findChild("participant", Namespace.MIX_CORE); + final String id = item.getAttribute("id"); + final Jid jid = participant == null ? null : participant.getAttributeAsJid("jid"); + Jid fullJid = conversation.getJid().asBareJid().withResource(id); + MucOptions.User user = new MucOptions.User(conversation.getMucOptions(), fullJid); + user.setRealJid(jid); + user.setAffiliation(MucOptions.Affiliation.MEMBER); + user.setRole(MucOptions.Role.PARTICIPANT); + return user; + } + public static MucOptions.User parseItem(Conversation conference, Element item) { return parseItem(conference,item, null); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 42b921bd1..557d61dda 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -49,10 +49,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece super(service); } - private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) { + private static String extractStanzaId(MessagePacket packet, boolean isTypeGroupChat, Conversation conversation) { final Jid by; final boolean safeToExtract; if (isTypeGroupChat) { + if (conversation.getMucOptions().isMix()) { + return packet.getId(); + } by = conversation.getJid().asBareJid(); safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS); } else { @@ -221,6 +224,32 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS); mXmppConnectionService.processBookmarks(account, storage); } + } else if ("urn:xmpp:mix:nodes:participants".equals(node)) { + Conversation conversation = mXmppConnectionService.find(account, from.asBareJid()); + if (conversation == null) { + Log.d(Config.LOGTAG,"not finding proper conversation for participant event"); + return; + } + for(Element item : items.getChildren()) { + if ("item".equals(item.getName())) { + MucOptions.User user = AbstractParser.parseParticipant(conversation, item); + if (conversation.getMucOptions().updateUser(user)) { + mXmppConnectionService.getAvatarService().clear(conversation); + } + Log.d(Config.LOGTAG,"updating user with full jid="+user.getFullJid()); + //Log.d(Config.LOGTAG,item.toString()); + } else if ("retract".equals(item.getName())) { + String id = item.getAttribute("id"); + if (id != null) { + Jid jid = conversation.getJid().asBareJid().withResource(id); + MucOptions.User user = conversation.getMucOptions().deleteUser(jid); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": retracting user "+jid); + if (user != null) { + mXmppConnectionService.getAvatarService().clear(conversation); + } + } + } + } } } @@ -387,20 +416,34 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (isTypeGroupChat) { - if (conversation.getMucOptions().isSelf(counterpart)) { - status = Message.STATUS_SEND_RECEIVED; - isCarbon = true; //not really carbon but received from another resource - if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) { - return; - } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { - Message message = conversation.findSentMessageWithBody(packet.getBody()); - if (message != null) { - mXmppConnectionService.markMessage(message, status); + if (conversation.getMucOptions().isMix()) { + if (account.getJid().asBareJid().equals(extractJid(packet))) { + status = Message.STATUS_SEND_RECEIVED; + isCarbon = true; + Element mix = packet.findChild("mix",Namespace.MIX_CORE); + String submissionId = mix == null ? null : mix.findChildContent("submission-id"); + if (submissionId != null && mXmppConnectionService.markMessage(conversation, submissionId, status, serverMsgId)) { return; } + } else { + status = Message.STATUS_RECEIVED; } } else { - status = Message.STATUS_RECEIVED; + if (conversation.getMucOptions().isSelf(counterpart)) { + status = Message.STATUS_SEND_RECEIVED; + isCarbon = true; //not really carbon but received from another resource + if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) { + return; + } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { + Message message = conversation.findSentMessageWithBody(packet.getBody()); + if (message != null) { + mXmppConnectionService.markMessage(message, status); + return; + } + } + } else { + status = Message.STATUS_RECEIVED; + } } } final Message message; @@ -491,14 +534,18 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversationMultiMode) { message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart)); - final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); - Jid trueCounterpart; - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - trueCounterpart = message.getTrueCounterpart(); - } else if (query != null && query.safeToExtractTrueCounterpart()) { - trueCounterpart = getTrueCounterpart(mucUserElement, fallback); + final Jid trueCounterpart; + if (conversation.getMucOptions().isMix()) { + trueCounterpart = extractJid(packet); } else { - trueCounterpart = fallback; + final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + trueCounterpart = message.getTrueCounterpart(); + } else if (query != null && query.safeToExtractTrueCounterpart()) { + trueCounterpart = getTrueCounterpart(mucUserElement, fallback); + } else { + trueCounterpart = fallback; + } } if (trueCounterpart != null && trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) { status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND; @@ -809,6 +856,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } + private static Jid extractJid(MessagePacket message) { + Element mix = message.findChild("mix",Namespace.MIX_CORE); + String jid = mix == null ? null : mix.findChildContent("jid"); + return jid == null ? null : Jid.of(jid); + } + private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query) { Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index dad6c558f..20a6f944b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1846,7 +1846,11 @@ public class XmppConnectionService extends Service { pushBookmarks(bookmark.getAccount()); } } - leaveMuc(conversation); + if (conversation.getMucOptions().isMix()) { + leaveMix(conversation); + } else { + leaveMuc(conversation); + } } else { if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { stopPresenceUpdatesTo(conversation.getContact()); @@ -2295,8 +2299,8 @@ public class XmppConnectionService extends Service { conversation.setHasMessagesLeftOnServer(false); fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { - private void join(Conversation conversation) { - Account account = conversation.getAccount(); + private void joinMuc(Conversation conversation) { + Account account = conversation.getAccount(); final MucOptions mucOptions = conversation.getMucOptions(); final Jid joinJid = mucOptions.getSelf().getFullJid(); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": joining conversation " + joinJid.toString()); @@ -2333,6 +2337,32 @@ public class XmppConnectionService extends Service { } } sendUnsentMessages(conversation); + } + + private void joinMix(Conversation conversation) { + IqPacket clientJoin = new IqPacket(IqPacket.TYPE.SET); + Element join = clientJoin.addChild("client-join",Namespace.MIX_PAM).setAttribute("channel",conversation.getJid().asBareJid().toEscapedString()).addChild("join",Namespace.MIX_CORE); + join.addChild("subscribe").setAttribute("node","urn:xmpp:mix:nodes:messages"); + join.addChild("subscribe").setAttribute("node","urn:xmpp:mix:nodes:participants"); + sendIqPacket(account, clientJoin, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + conversation.getMucOptions().setOnline(); + //Log.d(Config.LOGTAG,packet.toString()); + } + }); + fetchMixParticipants(conversation); + if (conversation.getMucOptions().mamSupport()) { + getMessageArchiveService().catchupMUC(conversation); + } + } + + private void join(Conversation conversation) { + if (conversation.getMucOptions().isMix()) { + joinMix(conversation); + } else { + joinMuc(conversation); + } } @Override @@ -2360,6 +2390,29 @@ public class XmppConnectionService extends Service { } } + private void fetchMixParticipants(final Conversation conversation) { + IqPacket fetch = new IqPacket(IqPacket.TYPE.GET); + fetch.setTo(conversation.getJid().asBareJid()); + fetch.addChild("pubsub",Namespace.PUBSUB).addChild("items").setAttribute("node","urn:xmpp:mix:nodes:participants"); + sendIqPacket(conversation.getAccount(), fetch, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + Element pubsub = response.findChild("pubsub",Namespace.PUBSUB); + Element items = pubsub == null ? null : pubsub.findChild("items"); + if (items != null) { + final MucOptions mucOptions = conversation.getMucOptions(); + for(Element item : items.getChildren()) { + if ("item".equals(item.getName())) { + MucOptions.User user = AbstractParser.parseParticipant(conversation, item); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": adding user with full jid="+user.getFullJid()+" after fetch"); + mucOptions.updateUser(user); + } + } + } + getAvatarService().clear(conversation); + } + }); + } + private void fetchConferenceMembers(final Conversation conversation) { final Account account = conversation.getAccount(); final AxolotlService axolotlService = account.getAxolotlService(); @@ -2527,6 +2580,14 @@ public class XmppConnectionService extends Service { return true; } + public void leaveMix(Conversation conversation) { + IqPacket request = new IqPacket(IqPacket.TYPE.SET); + Element clientLeave = request.addChild("client-leave",Namespace.MIX_PAM).setAttribute("channel",conversation.getJid().asBareJid().toEscapedString()); + clientLeave.addChild("leave",Namespace.MIX_CORE); + sendIqPacket(conversation.getAccount(), request, (account, packet) -> Log.d(Config.LOGTAG,packet.toString())); + + } + public void leaveMuc(Conversation conversation) { leaveMuc(conversation, false); } @@ -2574,6 +2635,32 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": creating adhoc conference with " + jids.toString()); if (account.getStatus() == Account.State.ONLINE) { try { + + List mixServers = account.getXmppConnection().getMixServers(); + if (mixServers.size() > 0) { + Log.d(Config.LOGTAG,"found mix servers: "+mixServers.get(0)); + IqPacket mixCreate = new IqPacket(IqPacket.TYPE.SET); + mixCreate.setTo(Jid.of(mixServers.get(0))); + mixCreate.addChild("create",Namespace.MIX_CORE);//.setAttribute("channel",CryptoHelper.pronounceable(getRNG())); + Log.d(Config.LOGTAG,mixCreate.toString()); + sendIqPacket(account, mixCreate, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + final Element create = packet.findChild("create",Namespace.MIX_CORE); + final String channel = create == null ? null : create.getAttribute("channel"); + if (channel != null) { + Jid jid = Jid.of(channel,packet.getFrom(), null); + findOrCreateConversation(account, jid, true, false); + } + } else { + Log.d(Config.LOGTAG, packet.toString()); + } + } + }); + return false; + } + String server = findConferenceServer(account); if (server == null) { if (callback != null) { diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 501acf89c..904637a26 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -24,4 +24,6 @@ public final class Namespace { public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0"; public static final String BOOKMARKS = "storage:bookmarks"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; + public static final String MIX_CORE = "urn:xmpp:mix:core:0"; + public static final String MIX_PAM = "urn:xmpp:mix:pam:0"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 94833a3b3..e7684b9f1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1360,6 +1360,7 @@ public class XmppConnection implements Runnable { } public void sendMessagePacket(final MessagePacket packet) { + Log.d(Config.LOGTAG,packet.toString()); this.sendPacket(packet); } @@ -1543,12 +1544,12 @@ public class XmppConnection implements Runnable { return servers; } - public List getMucServers() { + private List getGroupChatService(final String namespace) { List servers = new ArrayList<>(); synchronized (this.disco) { for (final Entry cursor : disco.entrySet()) { final ServiceDiscoveryResult value = cursor.getValue(); - if (value.getFeatures().contains("http://jabber.org/protocol/muc") + if (value.getFeatures().contains(namespace) && value.hasIdentity("conference", "text") && !value.getFeatures().contains("jabber:iq:gateway") && !value.hasIdentity("conference", "irc")) { @@ -1559,6 +1560,14 @@ public class XmppConnection implements Runnable { return servers; } + public List getMixServers() { + return getGroupChatService(Namespace.MIX_CORE); + } + + public List getMucServers() { + return getGroupChatService("http://jabber.org/protocol/muc"); + } + public String getMucServer() { List servers = getMucServers(); return servers.size() > 0 ? servers.get(0) : null; @@ -1801,6 +1810,10 @@ public class XmppConnection implements Runnable { return MessageArchiveService.Version.has(getAccountFeatures()); } + public boolean mixPam() { + return hasDiscoFeature(account.getJid().asBareJid(), Namespace.MIX_PAM); + } + public List getAccountFeatures() { ServiceDiscoveryResult result = connection.disco.get(account.getJid().asBareJid()); return result == null ? Collections.emptyList() : result.getFeatures(); -- cgit v1.2.3