return username;
}
+ public SignalServiceAddress getSelfAddress() {
+ return account.getSelfAddress();
+ }
+
private SignalServiceAccountManager getSignalServiceAccountManager() {
return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, null, account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
}
if (g == null) {
throw new GroupNotFoundException(groupId);
}
- for (String member : g.members) {
- if (member.equals(account.getUsername())) {
- return g;
- }
+ if (!g.isMember(account.getSelfAddress())) {
+ throw new NotAGroupMemberException(groupId, g.name);
}
- throw new NotAGroupMemberException(groupId, g.name);
+ return g;
}
public List<GroupInfo> getGroups() {
@Override
public void sendGroupMessage(String messageText, List<String> attachments,
byte[] groupId)
- throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) {
messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
final GroupInfo g = getGroupForSending(groupId);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
long targetSentTimestamp, byte[] groupId)
- throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withReaction(reaction)
messageBuilder.asGroupMessage(group);
}
final GroupInfo g = getGroupForSending(groupId);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, InvalidNumberException {
+ public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
.withId(groupId)
.build();
.asGroupMessage(group);
final GroupInfo g = getGroupForSending(groupId);
- g.members.remove(account.getUsername());
+ g.removeMember(account.getSelfAddress());
account.getGroupStore().updateGroup(g);
- sendMessageLegacy(messageBuilder, getSignalServiceAddresses(g.members));
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+ private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
GroupInfo g;
if (groupId == null) {
// Create new group
g = new GroupInfo(KeyUtils.createGroupId());
- g.members.add(account.getUsername());
+ g.addMembers(Collections.singleton(account.getSelfAddress()));
} else {
g = getGroupForSending(groupId);
}
}
if (members != null) {
- Set<String> newMembers = new HashSet<>();
- for (String member : members) {
- member = Utils.canonicalizeNumber(member, account.getUsername());
- if (g.members.contains(member)) {
+ final Set<String> newE164Members = new HashSet<>();
+ for (SignalServiceAddress member : members) {
+ if (g.isMember(member) || !member.getNumber().isPresent()) {
continue;
}
- newMembers.add(member);
- g.members.add(member);
+ newE164Members.add(member.getNumber().get());
}
- final List<ContactTokenDetails> contacts = accountManager.getContacts(newMembers);
- if (contacts.size() != newMembers.size()) {
+
+ final List<ContactTokenDetails> contacts = accountManager.getContacts(newE164Members);
+ if (contacts.size() != newE164Members.size()) {
// Some of the new members are not registered on Signal
for (ContactTokenDetails contact : contacts) {
- newMembers.remove(contact.getNumber());
+ newE164Members.remove(contact.getNumber());
}
- System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
+ System.err.println("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
System.err.println("Aborting…");
System.exit(1);
}
+
+ g.addMembers(members);
}
if (avatarFile != null) {
SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
return g.groupId;
}
}
GroupInfo g = getGroupForSending(groupId);
- if (!g.members.contains(recipient.getNumber().get())) {
+ if (!g.isMember(recipient)) {
return;
}
public List<String> getGroupMembers(byte[] groupId) {
GroupInfo group = getGroup(groupId);
if (group == null) {
- return new ArrayList<>();
+ return Collections.emptyList();
} else {
- return new ArrayList<>(group.members);
+ return new ArrayList<>(group.getMembersE164());
}
}
if (avatar.isEmpty()) {
avatar = null;
}
- return sendUpdateGroupMessage(groupId, name, members, avatar);
+ return sendUpdateGroupMessage(groupId, name, members == null ? null : getSignalServiceAddresses(members), avatar);
}
/**
e.printStackTrace();
}
} else {
- group.members.remove(source.getNumber().get());
+ group.removeMember(source);
account.getGroupStore().updateGroup(group);
}
break;
}
syncGroup.addMembers(g.getMembers());
if (!g.isActive()) {
- syncGroup.members.remove(account.getUsername());
+ syncGroup.removeMember(account.getSelfAddress());
} else {
// Add ourself to the member set as it's marked as active
- syncGroup.members.add(account.getUsername());
+ syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
}
syncGroup.blocked = g.isBlocked();
if (g.getColor().isPresent()) {
ThreadInfo info = account.getThreadStore().getThread(Base64.encodeBytes(record.groupId));
out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
- record.members.contains(account.getUsername()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+ record.isMember(account.getSelfAddress()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived));
}
}
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
+import java.util.UUID;
public class GroupInfo {
+ private static final ObjectMapper jsonProcessor = new ObjectMapper();
+
@JsonProperty
public final byte[] groupId;
public String name;
@JsonProperty
- public Set<String> members = new HashSet<>();
+ @JsonDeserialize(using = MembersDeserializer.class)
+ @JsonSerialize(using = MembersSerializer.class)
+ public Set<SignalServiceAddress> members = new HashSet<>();
@JsonProperty
public String color;
@JsonProperty(defaultValue = "false")
this.groupId = groupId;
}
- public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<String> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) {
+ public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<SignalServiceAddress> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) {
this.groupId = groupId;
this.name = name;
this.members.addAll(members);
@JsonIgnore
public Set<SignalServiceAddress> getMembers() {
- Set<SignalServiceAddress> addresses = new HashSet<>(members.size());
- for (String member : members) {
- addresses.add(new SignalServiceAddress(null, member));
- }
- return addresses;
+ return members;
}
- public void addMembers(Collection<SignalServiceAddress> members) {
+ @JsonIgnore
+ public Set<String> getMembersE164() {
+ Set<String> membersE164 = new HashSet<>();
for (SignalServiceAddress member : members) {
- this.members.add(member.getNumber().get());
+ if (!member.getNumber().isPresent()) {
+ continue;
+ }
+ membersE164.add(member.getNumber().get());
+ }
+ return membersE164;
+ }
+
+ @JsonIgnore
+ public Set<SignalServiceAddress> getMembersWithout(SignalServiceAddress address) {
+ Set<SignalServiceAddress> members = new HashSet<>(this.members.size());
+ for (SignalServiceAddress member : this.members) {
+ if (!member.matches(address)) {
+ members.add(member);
+ }
+ }
+ return members;
+ }
+
+ public void addMembers(Collection<SignalServiceAddress> addresses) {
+ for (SignalServiceAddress address : addresses) {
+ removeMember(address);
+ this.members.add(address);
+ }
+ }
+
+ public void removeMember(SignalServiceAddress address) {
+ this.members.removeIf(member -> member.matches(address));
+ }
+
+ @JsonIgnore
+ public boolean isMember(SignalServiceAddress address) {
+ for (SignalServiceAddress member : this.members) {
+ if (member.matches(address)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final class JsonSignalServiceAddress {
+
+ @JsonProperty
+ private UUID uuid;
+
+ @JsonProperty
+ private String number;
+
+ JsonSignalServiceAddress(@JsonProperty("uuid") final UUID uuid, @JsonProperty("number") final String number) {
+ this.uuid = uuid;
+ this.number = number;
+ }
+
+ JsonSignalServiceAddress(SignalServiceAddress address) {
+ this.uuid = address.getUuid().orNull();
+ this.number = address.getNumber().orNull();
+ }
+
+ SignalServiceAddress toSignalServiceAddress() {
+ return new SignalServiceAddress(uuid, number);
+ }
+ }
+
+ private static class MembersSerializer extends JsonSerializer<Set<SignalServiceAddress>> {
+
+ @Override
+ public void serialize(final Set<SignalServiceAddress> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
+ jgen.writeStartArray(value.size());
+ for (SignalServiceAddress address : value) {
+ if (address.getUuid().isPresent()) {
+ jgen.writeObject(new JsonSignalServiceAddress(address));
+ } else {
+ jgen.writeString(address.getNumber().get());
+ }
+ }
+ jgen.writeEndArray();
+ }
+ }
+
+ private static class MembersDeserializer extends JsonDeserializer<Set<SignalServiceAddress>> {
+
+ @Override
+ public Set<SignalServiceAddress> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ Set<SignalServiceAddress> addresses = new HashSet<>();
+ JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+ for (JsonNode n : node) {
+ if (n.isTextual()) {
+ addresses.add(new SignalServiceAddress(null, n.textValue()));
+ } else {
+ JsonSignalServiceAddress address = jsonProcessor.treeToValue(n, JsonSignalServiceAddress.class);
+ addresses.add(address.toSignalServiceAddress());
+ }
+ }
+
+ return addresses;
}
}
}