<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
</codeStyleSettings>
<codeStyleSettings language="XML">
- <option name="FORCE_REARRANGE_MODE" value="0" />
<arrangement>
<rules />
</arrangement>
the fingerprint.
updateProfile
---------------
+~~~~~~~~~~~~~
Update the name and/or avatar image visible by message recipients for the current users.
The profile is stored encrypted on the Signal servers. The decryption key is sent
with every outgoing messages (excluding group messages).
Remove the avatar visible by message recipients.
updateContact
---------------
+~~~~~~~~~~~~~
Update the info associated to a number on our contact list. This change is only
local but can be synchronized to other devices by using `sendContacts` (see
below).
*-n*, *--name*::
Specify the new name for this contact.
+block
+~~~~~
+Block the given contacts or groups (no messages will be received). This change is only
+local but can be synchronized to other devices by using `sendContacts` (see
+below).
+
+[CONTACT [CONTACT ...]]::
+ Specify the phone numbers of contacts that should be blocked.
+
+*-g* [GROUP [GROUP ...]], *--group* [GROUP [GROUP ...]]::
+ Specify the group IDs that should be blocked in base64 encoding.
+
+unblock
+~~~~~~~
+Unblock the given contacts or groups (messages will be received again). This change is only
+local but can be synchronized to other devices by using `sendContacts` (see
+below).
+
+[CONTACT [CONTACT ...]]::
+Specify the phone numbers of contacts that should be unblocked.
+
+*-g* [GROUP [GROUP ...]], *--group* [GROUP [GROUP ...]]::
+Specify the group IDs that should be unblocked in base64 encoding.
+
sendContacts
-------------
+~~~~~~~~~~~~
Send a synchronization message with the local contacts list to all linked devices.
This command should only be used if this is the master device.
void setContactName(String number, String name) throws InvalidNumberException;
+ void setContactBlocked(String number, boolean blocked) throws InvalidNumberException;
+
+ void setGroupBlocked(byte[] groupId, boolean blocked) throws GroupNotFoundException;
+
List<byte[]> getGroupIds();
String getGroupName(byte[] groupId);
--- /dev/null
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+import net.sourceforge.argparse4j.inf.Subparser;
+import org.asamk.signal.GroupIdFormatException;
+import org.asamk.signal.GroupNotFoundException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.util.Util;
+import org.whispersystems.signalservice.api.util.InvalidNumberException;
+
+public class BlockCommand implements LocalCommand {
+
+ @Override
+ public void attachToSubparser(final Subparser subparser) {
+ subparser.addArgument("contact")
+ .help("Contact number")
+ .nargs("*");
+ subparser.addArgument("-g", "--group")
+ .help("Group ID")
+ .nargs("*");
+ subparser.help("Block the given contacts or groups (no messages will be received)");
+ }
+
+ @Override
+ public int handleCommand(final Namespace ns, final Manager m) {
+ if (!m.isRegistered()) {
+ System.err.println("User is not registered.");
+ return 1;
+ }
+
+ for (String contact_number : ns.<String>getList("contact")) {
+ try {
+ m.setContactBlocked(contact_number, true);
+ } catch (InvalidNumberException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ if (ns.<String>getList("group") != null) {
+ for (String groupIdString : ns.<String>getList("group")) {
+ try {
+ byte[] groupId = Util.decodeGroupId(groupIdString);
+ m.setGroupBlocked(groupId, true);
+ } catch (GroupIdFormatException | GroupNotFoundException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+
+ return 0;
+ }
+}
static {
addCommand("addDevice", new AddDeviceCommand());
+ addCommand("block", new BlockCommand());
addCommand("daemon", new DaemonCommand());
addCommand("link", new LinkCommand());
addCommand("listContacts", new ListContactsCommand());
addCommand("updateContact", new UpdateContactCommand());
addCommand("setPin", new SetPinCommand());
addCommand("trust", new TrustCommand());
+ addCommand("unblock", new UnblockCommand());
addCommand("unregister", new UnregisterCommand());
addCommand("updateAccount", new UpdateAccountCommand());
addCommand("updateGroup", new UpdateGroupCommand());
}
List<ContactInfo> contacts = m.getContacts();
for (ContactInfo c : contacts) {
- System.out.println(String.format("Number: %s Name: %s", c.number, c.name));
+ System.out.println(String.format("Number: %s Name: %s Blocked: %b", c.number, c.name, c.blocked));
}
return 0;
}
private static void printGroup(GroupInfo group, boolean detailed) {
if (detailed) {
- System.out.println(String.format("Id: %s Name: %s Active: %s Members: %s",
- Base64.encodeBytes(group.groupId), group.name, group.active, group.members));
+ System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b Members: %s",
+ Base64.encodeBytes(group.groupId), group.name, group.active, group.blocked, group.members));
} else {
- System.out.println(String.format("Id: %s Name: %s Active: %s", Base64.encodeBytes(group.groupId),
- group.name, group.active));
+ System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b",
+ Base64.encodeBytes(group.groupId), group.name, group.active, group.blocked));
}
}
--- /dev/null
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+import net.sourceforge.argparse4j.inf.Subparser;
+import org.asamk.signal.GroupIdFormatException;
+import org.asamk.signal.GroupNotFoundException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.util.Util;
+import org.whispersystems.signalservice.api.util.InvalidNumberException;
+
+public class UnblockCommand implements LocalCommand {
+
+ @Override
+ public void attachToSubparser(final Subparser subparser) {
+ subparser.addArgument("contact")
+ .help("Contact number")
+ .nargs("*");
+ subparser.addArgument("-g", "--group")
+ .help("Group ID")
+ .nargs("*");
+ subparser.help("Unblock the given contacts or groups (messages will be received again)");
+ }
+
+ @Override
+ public int handleCommand(final Namespace ns, final Manager m) {
+ if (!m.isRegistered()) {
+ System.err.println("User is not registered.");
+ return 1;
+ }
+
+ for (String contact_number : ns.<String>getList("contact")) {
+ try {
+ m.setContactBlocked(contact_number, false);
+ } catch (InvalidNumberException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ if (ns.<String>getList("group") != null) {
+ for (String groupIdString : ns.<String>getList("group")) {
+ try {
+ byte[] groupId = Util.decodeGroupId(groupIdString);
+ m.setGroupBlocked(groupId, false);
+ } catch (GroupIdFormatException | GroupNotFoundException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+
+ return 0;
+ }
+}
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream;
account.save();
}
+ @Override
+ public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException {
+ number = Utils.canonicalizeNumber(number, username);
+ ContactInfo contact = account.getContactStore().getContact(number);
+ if (contact == null) {
+ contact = new ContactInfo();
+ contact.number = number;
+ System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + number);
+ } else {
+ System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + number);
+ }
+ contact.blocked = blocked;
+ account.getContactStore().updateContact(contact);
+ account.save();
+ }
+
+ @Override
+ public void setGroupBlocked(final byte[] groupId, final boolean blocked) throws GroupNotFoundException {
+ GroupInfo group = getGroup(groupId);
+ if (group == null) {
+ throw new GroupNotFoundException(groupId);
+ } else {
+ System.err.println((blocked ? "Blocking" : "Unblocking") + " group " + Base64.encodeBytes(groupId));
+ group.blocked = blocked;
+ account.getGroupStore().updateGroup(group);
+ account.save();
+ }
+ }
+
@Override
public List<byte[]> getGroupIds() {
List<GroupInfo> groups = getGroups();
handleMessage(envelope, content, ignoreAttachments);
}
account.save();
- handler.handleMessage(envelope, content, exception);
+ if (!isMessageBlocked(envelope, content)) {
+ handler.handleMessage(envelope, content, exception);
+ }
if (!(exception instanceof ProtocolUntrustedIdentityException)) {
File cacheFile = null;
try {
}
}
+ private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
+ SignalServiceAddress source;
+ if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
+ source = envelope.getSourceAddress();
+ } else if (content != null) {
+ source = content.getSender();
+ } else {
+ return false;
+ }
+ ContactInfo sourceContact = getContact(source.getNumber().get());
+ if (sourceContact != null && sourceContact.blocked) {
+ return true;
+ }
+
+ if (content != null && content.getDataMessage().isPresent()) {
+ SignalServiceDataMessage message = content.getDataMessage().get();
+ if (message.getGroupInfo().isPresent()) {
+ SignalServiceGroup groupInfo = message.getGroupInfo().get();
+ GroupInfo group = getGroup(groupInfo.getGroupId());
+ if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments) {
if (content != null) {
SignalServiceAddress sender;
e.printStackTrace();
}
}
- // TODO Handle rm.isBlockedListRequest(); rm.isConfigurationRequest();
+ if (rm.isBlockedListRequest()) {
+ try {
+ sendBlockedList();
+ } catch (UntrustedIdentityException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+ // TODO Handle rm.isConfigurationRequest();
}
if (syncMessage.getGroups().isPresent()) {
File tmpFile = null;
}
syncGroup.addMembers(g.getMembers());
syncGroup.active = g.isActive();
+ syncGroup.blocked = g.isBlocked();
if (g.getColor().isPresent()) {
syncGroup.color = g.getColor().get();
}
}
}
if (syncMessage.getBlockedList().isPresent()) {
- // TODO store list of blocked numbers
+ final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get();
+ for (SignalServiceAddress address : blockedListMessage.getAddresses()) {
+ if (address.getNumber().isPresent()) {
+ try {
+ setContactBlocked(address.getNumber().get(), true);
+ } catch (InvalidNumberException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ for (byte[] groupId : blockedListMessage.getGroupIds()) {
+ try {
+ setGroupBlocked(groupId, true);
+ } catch (GroupNotFoundException e) {
+ System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64.encodeBytes(groupId));
+ }
+ }
}
if (syncMessage.getContacts().isPresent()) {
File tmpFile = null;
thread.messageExpirationTime = c.getExpirationTimer().get();
account.getThreadStore().updateThread(thread);
}
- if (c.isBlocked()) {
- // TODO store list of blocked numbers
- }
+ contact.blocked = c.isBlocked();
account.getContactStore().updateContact(contact);
if (c.getAvatar().isPresent()) {
out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
record.active, Optional.fromNullable(info != null ? info.messageExpirationTime : null),
- Optional.fromNullable(record.color), false));
+ Optional.fromNullable(record.color), record.blocked));
}
}
}
byte[] profileKey = record.profileKey == null ? null : Base64.decode(record.profileKey);
- // TODO store list of blocked numbers
- boolean blocked = false;
out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name),
createContactAvatarAttachment(record.number), Optional.fromNullable(record.color),
- Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), blocked, Optional.fromNullable(info != null ? info.messageExpirationTime : null)));
+ Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked,
+ Optional.fromNullable(info != null ? info.messageExpirationTime : null)));
}
if (account.getProfileKey() != null) {
}
}
+ private void sendBlockedList() throws IOException, UntrustedIdentityException {
+ List<SignalServiceAddress> addresses = new ArrayList<>();
+ for (ContactInfo record : account.getContactStore().getContacts()) {
+ if (record.blocked) {
+ addresses.add(record.getAddress());
+ }
+ }
+ List<byte[]> groupIds = new ArrayList<>();
+ for (GroupInfo record : account.getGroupStore().getGroups()) {
+ if (record.blocked) {
+ groupIds.add(record.groupId);
+ }
+ }
+ sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds)));
+ }
+
private void sendVerifiedMessage(SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel) throws IOException, UntrustedIdentityException {
VerifiedMessage verifiedMessage = new VerifiedMessage(destination, identityKey, trustLevel.toVerifiedState(), System.currentTimeMillis());
sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage));
@JsonProperty
public String profileKey;
+ @JsonProperty(defaultValue = "false")
+ public boolean blocked;
+
@JsonIgnore
public SignalServiceAddress getAddress() {
return new SignalServiceAddress(null, number);
public boolean active;
@JsonProperty
public String color;
+ @JsonProperty(defaultValue = "false")
+ public boolean blocked;
private long avatarId;
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) {
+ 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) {
this.groupId = groupId;
this.name = name;
this.members.addAll(members);
this.avatarId = avatarId;
this.color = color;
+ this.blocked = blocked;
}
@JsonIgnore