mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Store group member uuids in group store
The member list is now stored as a mixed list of strings and objects, e.g.: "members": [ "+XXXX", { "number": "+XXXX", "uuid": "XXX-XX" } ]
This commit is contained in:
parent
a4e1d69788
commit
f982d2752e
5 changed files with 159 additions and 61 deletions
|
@ -19,7 +19,7 @@ public interface Signal extends DBusInterface {
|
|||
|
||||
void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException;
|
||||
|
||||
void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException, InvalidNumberException;
|
||||
void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException;
|
||||
|
||||
String getContactName(String number) throws InvalidNumberException;
|
||||
|
||||
|
|
|
@ -6,19 +6,20 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
|||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.storage.groups.GroupInfo;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ListGroupsCommand implements LocalCommand {
|
||||
|
||||
private static void printGroup(GroupInfo group, boolean detailed, String username) {
|
||||
private static void printGroup(GroupInfo group, boolean detailed, SignalServiceAddress address) {
|
||||
if (detailed) {
|
||||
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b Members: %s",
|
||||
Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked, group.members));
|
||||
Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked, group.getMembersE164()));
|
||||
} else {
|
||||
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b",
|
||||
Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked));
|
||||
Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +41,7 @@ public class ListGroupsCommand implements LocalCommand {
|
|||
boolean detailed = ns.getBoolean("detailed");
|
||||
|
||||
for (GroupInfo group : groups) {
|
||||
printGroup(group, detailed, m.getUsername());
|
||||
printGroup(group, detailed, m.getSelfAddress());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.asamk.signal.NotAGroupMemberException;
|
|||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -18,7 +17,6 @@ import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions;
|
|||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||
|
||||
public class QuitGroupCommand implements LocalCommand {
|
||||
|
@ -58,9 +56,6 @@ public class QuitGroupCommand implements LocalCommand {
|
|||
} catch (GroupIdFormatException e) {
|
||||
handleGroupIdFormatException(e);
|
||||
return 1;
|
||||
} catch (InvalidNumberException e) {
|
||||
handleInvalidNumberException(e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,6 +171,10 @@ public class Manager implements Signal {
|
|||
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);
|
||||
}
|
||||
|
@ -499,12 +503,10 @@ public class Manager implements Signal {
|
|||
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() {
|
||||
|
@ -514,7 +516,7 @@ public class Manager implements Signal {
|
|||
@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));
|
||||
|
@ -532,15 +534,12 @@ public class Manager implements Signal {
|
|||
|
||||
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)
|
||||
|
@ -552,13 +551,10 @@ public class Manager implements Signal {
|
|||
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();
|
||||
|
@ -567,18 +563,18 @@ public class Manager implements Signal {
|
|||
.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);
|
||||
}
|
||||
|
@ -588,25 +584,26 @@ public class Manager implements Signal {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -619,10 +616,7 @@ public class Manager implements Signal {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -632,7 +626,7 @@ public class Manager implements Signal {
|
|||
}
|
||||
GroupInfo g = getGroupForSending(groupId);
|
||||
|
||||
if (!g.members.contains(recipient.getNumber().get())) {
|
||||
if (!g.isMember(recipient)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -819,9 +813,9 @@ public class Manager implements Signal {
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -839,7 +833,7 @@ public class Manager implements Signal {
|
|||
if (avatar.isEmpty()) {
|
||||
avatar = null;
|
||||
}
|
||||
return sendUpdateGroupMessage(groupId, name, members, avatar);
|
||||
return sendUpdateGroupMessage(groupId, name, members == null ? null : getSignalServiceAddresses(members), avatar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1284,7 +1278,7 @@ public class Manager implements Signal {
|
|||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
group.members.remove(source.getNumber().get());
|
||||
group.removeMember(source);
|
||||
account.getGroupStore().updateGroup(group);
|
||||
}
|
||||
break;
|
||||
|
@ -1559,10 +1553,10 @@ public class Manager implements Signal {
|
|||
}
|
||||
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()) {
|
||||
|
@ -1778,7 +1772,7 @@ public class Manager implements Signal {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,29 @@ package org.asamk.signal.storage.groups;
|
|||
|
||||
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;
|
||||
|
||||
|
@ -18,7 +32,9 @@ public class GroupInfo {
|
|||
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")
|
||||
|
@ -38,7 +54,7 @@ public class GroupInfo {
|
|||
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);
|
||||
|
@ -56,16 +72,108 @@ public class GroupInfo {
|
|||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue