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.PresenceShow;
import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
import java.util.Arrays;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart;
@ -37,7 +38,10 @@ public abstract class PresenceDao {
@NonNull final Resourcepart resource,
@Nullable final PresenceType type,
@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)
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
deletePresences(account.id, address);
@ -49,7 +53,17 @@ public abstract class PresenceDao {
// unavailable presence only delete previous nothing left to do
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);
}
}

View file

@ -10,6 +10,7 @@ import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xmpp.model.muc.Affiliation;
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.Jid;
import org.jxmpp.jid.parts.Resourcepart;
@ -70,9 +71,13 @@ public class PresenceEntity {
long account,
@NonNull BareJid address,
@NonNull Resourcepart resource,
PresenceType type,
PresenceShow show,
String status) {
final PresenceType type,
final PresenceShow show,
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();
entity.accountId = account;
entity.address = address;
@ -80,6 +85,14 @@ public class PresenceEntity {
entity.type = type;
entity.show = show;
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;
}
}

View file

@ -229,7 +229,7 @@ public class Resolver {
}
private static List<ServiceRecord> resolveNoSrvRecords(DNSName dnsName, boolean includeCName) {
List<ServiceRecord> results = new ArrayList<>();
var results = new ImmutableList.Builder<ServiceRecord>();
try {
for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
results.add(ServiceRecord.createDefault(dnsName, a.getInetAddress()));
@ -238,17 +238,17 @@ public class Resolver {
resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
results.add(ServiceRecord.createDefault(dnsName, aaaa.getInetAddress()));
}
if (results.size() == 0 && includeCName) {
if (results.build().isEmpty() && includeCName) {
for (CNAME cname :
resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
results.addAll(resolveNoSrvRecords(cname.name, false));
}
}
} catch (Throwable throwable) {
} catch (final Throwable throwable) {
LOGGER.info("Error resolving fallback records", throwable);
}
results.add(ServiceRecord.createDefault(dnsName));
return results;
return results.build();
}
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 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 VCARD_TEMP = "vcard-temp";
public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update";
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.HttpUploadManager;
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.PepManager;
import im.conversations.android.xmpp.manager.PresenceManager;
@ -45,6 +46,7 @@ public final class Managers {
.put(
JingleConnectionManager.class,
new JingleConnectionManager(context, connection))
.put(MultiUserChatManager.class, new MultiUserChatManager(context, connection))
.put(NickManager.class, new NickManager(context, connection))
.put(PepManager.class, new PepManager(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;
import com.google.common.collect.Collections2;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
import java.util.Collection;
import java.util.Objects;
@XmlElement(name = "x")
public class MultiUserChat extends Extension {
@ -9,4 +12,14 @@ public class MultiUserChat extends Extension {
public MultiUserChat() {
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 im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.XmppConnection;
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.vcard.update.VCardUpdate;
import java.util.function.Consumer;
import org.slf4j.Logger;
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 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?
fetchCapabilities(presencePacket);