parse more presence metadata

This commit is contained in:
Daniel Gultsch 2023-03-03 12:05:20 +01:00
parent 2e5e2ff6fe
commit 8be8d7df8f
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
15 changed files with 249 additions and 11 deletions

View file

@ -10,6 +10,7 @@ import im.conversations.android.database.entity.PresenceEntity;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.PresenceShow; import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType; import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
import java.util.Arrays; import java.util.Arrays;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.parts.Resourcepart;
@ -37,7 +38,10 @@ public abstract class PresenceDao {
@NonNull final Resourcepart resource, @NonNull final Resourcepart resource,
@Nullable final PresenceType type, @Nullable final PresenceType type,
@Nullable final PresenceShow show, @Nullable final PresenceShow show,
@Nullable final String status) { @Nullable final String status,
@Nullable final String vCardPhoto,
@Nullable final String occupantId,
@Nullable final MultiUserChat multiUserChat) {
if (resource.equals(Resourcepart.EMPTY) if (resource.equals(Resourcepart.EMPTY)
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) { && Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
deletePresences(account.id, address); deletePresences(account.id, address);
@ -49,7 +53,17 @@ public abstract class PresenceDao {
// unavailable presence only delete previous nothing left to do // unavailable presence only delete previous nothing left to do
return; return;
} }
final var entity = PresenceEntity.of(account.id, address, resource, type, show, status); final var entity =
PresenceEntity.of(
account.id,
address,
resource,
type,
show,
status,
vCardPhoto,
occupantId,
multiUserChat);
insert(entity); insert(entity);
} }
} }

View file

@ -10,6 +10,7 @@ import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType; import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xmpp.model.muc.Affiliation; import im.conversations.android.xmpp.model.muc.Affiliation;
import im.conversations.android.xmpp.model.muc.Role; import im.conversations.android.xmpp.model.muc.Role;
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.parts.Resourcepart;
@ -70,9 +71,13 @@ public class PresenceEntity {
long account, long account,
@NonNull BareJid address, @NonNull BareJid address,
@NonNull Resourcepart resource, @NonNull Resourcepart resource,
PresenceType type, final PresenceType type,
PresenceShow show, final PresenceShow show,
String status) { final String status,
final String vCardPhoto,
final String occupantId,
final MultiUserChat multiUserChat) {
final var mucItem = multiUserChat == null ? null : multiUserChat.getItem();
final var entity = new PresenceEntity(); final var entity = new PresenceEntity();
entity.accountId = account; entity.accountId = account;
entity.address = address; entity.address = address;
@ -80,6 +85,14 @@ public class PresenceEntity {
entity.type = type; entity.type = type;
entity.show = show; entity.show = show;
entity.status = status; entity.status = status;
entity.vCardPhoto = vCardPhoto;
if (mucItem != null) {
entity.occupantId = occupantId;
entity.mucUserAffiliation = mucItem.getAffiliation();
entity.mucUserRole = mucItem.getRole();
entity.mucUserJid = mucItem.getJid();
entity.mucUserSelf = multiUserChat.getStatus().contains(110);
}
return entity; return entity;
} }
} }

View file

@ -229,7 +229,7 @@ public class Resolver {
} }
private static List<ServiceRecord> resolveNoSrvRecords(DNSName dnsName, boolean includeCName) { private static List<ServiceRecord> resolveNoSrvRecords(DNSName dnsName, boolean includeCName) {
List<ServiceRecord> results = new ArrayList<>(); var results = new ImmutableList.Builder<ServiceRecord>();
try { try {
for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) { for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
results.add(ServiceRecord.createDefault(dnsName, a.getInetAddress())); results.add(ServiceRecord.createDefault(dnsName, a.getInetAddress()));
@ -238,17 +238,17 @@ public class Resolver {
resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) { resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
results.add(ServiceRecord.createDefault(dnsName, aaaa.getInetAddress())); results.add(ServiceRecord.createDefault(dnsName, aaaa.getInetAddress()));
} }
if (results.size() == 0 && includeCName) { if (results.build().isEmpty() && includeCName) {
for (CNAME cname : for (CNAME cname :
resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) { resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
results.addAll(resolveNoSrvRecords(cname.name, false)); results.addAll(resolveNoSrvRecords(cname.name, false));
} }
} }
} catch (Throwable throwable) { } catch (final Throwable throwable) {
LOGGER.info("Error resolving fallback records", throwable); LOGGER.info("Error resolving fallback records", throwable);
} }
results.add(ServiceRecord.createDefault(dnsName)); results.add(ServiceRecord.createDefault(dnsName));
return results; return results.build();
} }
private static ResolverResult<SRV> resolveWithFallback( private static ResolverResult<SRV> resolveWithFallback(

View file

@ -99,5 +99,7 @@ public final class Namespace {
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
public static final String VCARD_TEMP = "vcard-temp";
public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update";
public static final String VERSION = "jabber:iq:version"; public static final String VERSION = "jabber:iq:version";
} }

View file

@ -15,6 +15,7 @@ import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.manager.ExternalDiscoManager; import im.conversations.android.xmpp.manager.ExternalDiscoManager;
import im.conversations.android.xmpp.manager.HttpUploadManager; import im.conversations.android.xmpp.manager.HttpUploadManager;
import im.conversations.android.xmpp.manager.JingleConnectionManager; import im.conversations.android.xmpp.manager.JingleConnectionManager;
import im.conversations.android.xmpp.manager.MultiUserChatManager;
import im.conversations.android.xmpp.manager.NickManager; import im.conversations.android.xmpp.manager.NickManager;
import im.conversations.android.xmpp.manager.PepManager; import im.conversations.android.xmpp.manager.PepManager;
import im.conversations.android.xmpp.manager.PresenceManager; import im.conversations.android.xmpp.manager.PresenceManager;
@ -45,6 +46,7 @@ public final class Managers {
.put( .put(
JingleConnectionManager.class, JingleConnectionManager.class,
new JingleConnectionManager(context, connection)) new JingleConnectionManager(context, connection))
.put(MultiUserChatManager.class, new MultiUserChatManager(context, connection))
.put(NickManager.class, new NickManager(context, connection)) .put(NickManager.class, new NickManager(context, connection))
.put(PepManager.class, new PepManager(context, connection)) .put(PepManager.class, new PepManager(context, connection))
.put(PresenceManager.class, new PresenceManager(context, connection)) .put(PresenceManager.class, new PresenceManager(context, connection))

View file

@ -0,0 +1,28 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
import im.conversations.android.xmpp.model.stanza.Presence;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiUserChatManager extends AbstractManager {
private static final Logger LOGGER = LoggerFactory.getLogger(MultiUserChatManager.class);
public MultiUserChatManager(Context context, XmppConnection connection) {
super(context, connection);
}
public void enter(final BareJid room) {
final var presence = new Presence();
presence.setTo(JidCreate.fullFrom(room, Resourcepart.fromOrThrowUnchecked("c3-test-user")));
presence.addExtension(new MultiUserChat());
LOGGER.info("sending {} ", presence);
connection.sendPresencePacket(presence);
}
}

View file

@ -0,0 +1,65 @@
package im.conversations.android.xmpp.model.muc.user;
import com.google.common.base.Strings;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.muc.Affiliation;
import im.conversations.android.xmpp.model.muc.Role;
import java.util.Locale;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@XmlElement
public class Item extends Extension {
private static final Logger LOGGER = LoggerFactory.getLogger(Item.class);
public Item() {
super(Item.class);
}
public Affiliation getAffiliation() {
final var affiliation = this.getAttribute("affiliation");
if (Strings.isNullOrEmpty(affiliation)) {
return Affiliation.NONE;
}
try {
return Affiliation.valueOf(affiliation.toUpperCase(Locale.ROOT));
} catch (final IllegalArgumentException e) {
LOGGER.warn("could not parse affiliation {}", affiliation);
return Affiliation.NONE;
}
}
public Role getRole() {
final var role = this.getAttribute("role");
if (Strings.isNullOrEmpty(role)) {
return Role.NONE;
}
try {
return Role.valueOf(role.toUpperCase(Locale.ROOT));
} catch (final IllegalArgumentException e) {
LOGGER.warn("could not parse role {}", role);
return Role.NONE;
}
}
public String getNick() {
return this.getAttribute("nick");
}
public Jid getJid() {
final var jid = this.getAttribute("jid");
if (Strings.isNullOrEmpty(jid)) {
return null;
}
try {
return JidCreate.from(jid);
} catch (final XmppStringprepException e) {
return null;
}
}
}

View file

@ -1,7 +1,10 @@
package im.conversations.android.xmpp.model.muc.user; package im.conversations.android.xmpp.model.muc.user;
import com.google.common.collect.Collections2;
import im.conversations.android.annotation.XmlElement; import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
import java.util.Collection;
import java.util.Objects;
@XmlElement(name = "x") @XmlElement(name = "x")
public class MultiUserChat extends Extension { public class MultiUserChat extends Extension {
@ -9,4 +12,14 @@ public class MultiUserChat extends Extension {
public MultiUserChat() { public MultiUserChat() {
super(MultiUserChat.class); super(MultiUserChat.class);
} }
public Item getItem() {
return this.getExtension(Item.class);
}
public Collection<Integer> getStatus() {
return Collections2.filter(
Collections2.transform(getExtensions(Status.class), Status::getCode),
Objects::nonNull);
}
} }

View file

@ -0,0 +1,16 @@
package im.conversations.android.xmpp.model.muc.user;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Status extends Extension {
public Status() {
super(Status.class);
}
public Integer getCode() {
return this.getOptionalIntAttribute("code").orNull();
}
}

View file

@ -0,0 +1,12 @@
package im.conversations.android.xmpp.model.vcard;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class VCard extends Extension {
public VCard() {
super(VCard.class);
}
}

View file

@ -0,0 +1,5 @@
@XmlPackage(namespace = Namespace.VCARD_TEMP)
package im.conversations.android.xmpp.model.vcard;
import im.conversations.android.annotation.XmlPackage;
import im.conversations.android.xml.Namespace;

View file

@ -0,0 +1,12 @@
package im.conversations.android.xmpp.model.vcard.update;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Photo extends Extension {
public Photo() {
super(Photo.class);
}
}

View file

@ -0,0 +1,21 @@
package im.conversations.android.xmpp.model.vcard.update;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement(name = "x")
public class VCardUpdate extends Extension {
public VCardUpdate() {
super(VCardUpdate.class);
}
public Photo getPhoto() {
return this.getExtension(Photo.class);
}
public String getHash() {
final var photo = getPhoto();
return photo == null ? null : photo.getContent();
}
}

View file

@ -0,0 +1,5 @@
@XmlPackage(namespace = Namespace.VCARD_TEMP_UPDATE)
package im.conversations.android.xmpp.model.vcard.update;
import im.conversations.android.annotation.XmlPackage;
import im.conversations.android.xml.Namespace;

View file

@ -3,10 +3,14 @@ package im.conversations.android.xmpp.processor;
import android.content.Context; import android.content.Context;
import im.conversations.android.database.model.PresenceShow; import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType; import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xmpp.Entity; import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
import im.conversations.android.xmpp.model.occupant.OccupantId;
import im.conversations.android.xmpp.model.stanza.Presence; import im.conversations.android.xmpp.model.stanza.Presence;
import im.conversations.android.xmpp.model.vcard.update.VCardUpdate;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -38,9 +42,35 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
} }
final var show = PresenceShow.of(presencePacket.findChildContent("show")); final var show = PresenceShow.of(presencePacket.findChildContent("show"));
final var status = presencePacket.findChildContent("status"); final var status = presencePacket.findChildContent("status");
getDatabase().presenceDao().set(getAccount(), address, resource, type, show, status);
// TODO store presence info (vCard + muc#user stuff + occupantId) final var vCardUpdate = presencePacket.getExtension(VCardUpdate.class);
final var vCardPhoto = vCardUpdate == null ? null : vCardUpdate.getHash();
final var muc = presencePacket.getExtension(MultiUserChat.class);
final String occupantId;
if (muc != null && presencePacket.hasExtension(OccupantId.class)) {
if (getManager(DiscoManager.class)
.hasFeature(Entity.discoItem(address), Namespace.OCCUPANT_ID)) {
occupantId = presencePacket.getExtension(OccupantId.class).getId();
} else {
occupantId = null;
}
} else {
occupantId = null;
}
getDatabase()
.presenceDao()
.set(
getAccount(),
address,
resource,
type,
show,
status,
vCardPhoto,
occupantId,
muc);
// TODO do this only for contacts? // TODO do this only for contacts?
fetchCapabilities(presencePacket); fetchCapabilities(presencePacket);