From: AsamK Date: Thu, 7 Oct 2021 19:18:14 +0000 (+0200) Subject: Implement new dbus group interface X-Git-Tag: v0.9.1~21 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/997b4f0c3fe371efe08e92ed4678835bc62538bf?ds=inline Implement new dbus group interface --- diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 7a421966..f529b408 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -8,13 +8,12 @@ import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; -import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupNotFoundException; -import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; @@ -138,20 +137,7 @@ public interface Manager extends Closeable { ) throws IOException, AttachmentInvalidException; SendGroupMessageResults updateGroup( - GroupId groupId, - String name, - String description, - Set members, - Set removeMembers, - Set admins, - Set removeAdmins, - boolean resetGroupLink, - GroupLinkState groupLinkState, - GroupPermission addMemberPermission, - GroupPermission editDetailsPermission, - File avatarFile, - Integer expirationTimer, - Boolean isAnnouncementGroup + final GroupId groupId, final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException; Pair joinGroup( diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 0fd1eb33..8ccab36b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -25,13 +25,12 @@ import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; -import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupNotFoundException; -import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; @@ -505,9 +504,12 @@ public class ManagerImpl implements Manager { .map(account.getRecipientStore()::resolveRecipientAddress) .collect(Collectors.toSet()), groupInfo.isBlocked(), - groupInfo.getMessageExpirationTime(), - groupInfo.isAnnouncementGroup(), - groupInfo.isMember(account.getSelfRecipientId())); + groupInfo.getMessageExpirationTimer(), + groupInfo.getPermissionAddMember(), + groupInfo.getPermissionEditDetails(), + groupInfo.getPermissionSendMessage(), + groupInfo.isMember(account.getSelfRecipientId()), + groupInfo.isAdmin(account.getSelfRecipientId())); } @Override @@ -532,35 +534,22 @@ public class ManagerImpl implements Manager { @Override public SendGroupMessageResults updateGroup( - GroupId groupId, - String name, - String description, - Set members, - Set removeMembers, - Set admins, - Set removeAdmins, - boolean resetGroupLink, - GroupLinkState groupLinkState, - GroupPermission addMemberPermission, - GroupPermission editDetailsPermission, - File avatarFile, - Integer expirationTimer, - Boolean isAnnouncementGroup + final GroupId groupId, final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { return groupHelper.updateGroup(groupId, - name, - description, - members == null ? null : resolveRecipients(members), - removeMembers == null ? null : resolveRecipients(removeMembers), - admins == null ? null : resolveRecipients(admins), - removeAdmins == null ? null : resolveRecipients(removeAdmins), - resetGroupLink, - groupLinkState, - addMemberPermission, - editDetailsPermission, - avatarFile, - expirationTimer, - isAnnouncementGroup); + updateGroup.getName(), + updateGroup.getDescription(), + updateGroup.getMembers() == null ? null : resolveRecipients(updateGroup.getMembers()), + updateGroup.getRemoveMembers() == null ? null : resolveRecipients(updateGroup.getRemoveMembers()), + updateGroup.getAdmins() == null ? null : resolveRecipients(updateGroup.getAdmins()), + updateGroup.getRemoveAdmins() == null ? null : resolveRecipients(updateGroup.getRemoveAdmins()), + updateGroup.isResetGroupLink(), + updateGroup.getGroupLinkState(), + updateGroup.getAddMemberPermission(), + updateGroup.getEditDetailsPermission(), + updateGroup.getAvatarFile(), + updateGroup.getExpirationTimer(), + updateGroup.getIsAnnouncementGroup()); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Group.java b/lib/src/main/java/org/asamk/signal/manager/api/Group.java index 650e10b6..4787ef95 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Group.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Group.java @@ -2,6 +2,7 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import java.util.Set; @@ -17,9 +18,13 @@ public class Group { private final Set requestingMembers; private final Set adminMembers; private final boolean isBlocked; - private final int messageExpirationTime; - private final boolean isAnnouncementGroup; + private final int messageExpirationTimer; + + private final GroupPermission permissionAddMember; + private final GroupPermission permissionEditDetails; + private final GroupPermission permissionSendMessage; private final boolean isMember; + private final boolean isAdmin; public Group( final GroupId groupId, @@ -31,9 +36,12 @@ public class Group { final Set requestingMembers, final Set adminMembers, final boolean isBlocked, - final int messageExpirationTime, - final boolean isAnnouncementGroup, - final boolean isMember + final int messageExpirationTimer, + final GroupPermission permissionAddMember, + final GroupPermission permissionEditDetails, + final GroupPermission permissionSendMessage, + final boolean isMember, + final boolean isAdmin ) { this.groupId = groupId; this.title = title; @@ -44,9 +52,12 @@ public class Group { this.requestingMembers = requestingMembers; this.adminMembers = adminMembers; this.isBlocked = isBlocked; - this.messageExpirationTime = messageExpirationTime; - this.isAnnouncementGroup = isAnnouncementGroup; + this.messageExpirationTimer = messageExpirationTimer; + this.permissionAddMember = permissionAddMember; + this.permissionEditDetails = permissionEditDetails; + this.permissionSendMessage = permissionSendMessage; this.isMember = isMember; + this.isAdmin = isAdmin; } public GroupId getGroupId() { @@ -85,15 +96,27 @@ public class Group { return isBlocked; } - public int getMessageExpirationTime() { - return messageExpirationTime; + public int getMessageExpirationTimer() { + return messageExpirationTimer; + } + + public GroupPermission getPermissionAddMember() { + return permissionAddMember; } - public boolean isAnnouncementGroup() { - return isAnnouncementGroup; + public GroupPermission getPermissionEditDetails() { + return permissionEditDetails; + } + + public GroupPermission getPermissionSendMessage() { + return permissionSendMessage; } public boolean isMember() { return isMember; } + + public boolean isAdmin() { + return isAdmin; + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java new file mode 100644 index 00000000..b5877ae5 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java @@ -0,0 +1,203 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.groups.GroupLinkState; +import org.asamk.signal.manager.groups.GroupPermission; + +import java.io.File; +import java.util.Set; + +public class UpdateGroup { + + private final String name; + private final String description; + private final Set members; + private final Set removeMembers; + private final Set admins; + private final Set removeAdmins; + private final boolean resetGroupLink; + private final GroupLinkState groupLinkState; + private final GroupPermission addMemberPermission; + private final GroupPermission editDetailsPermission; + private final File avatarFile; + private final Integer expirationTimer; + private final Boolean isAnnouncementGroup; + + private UpdateGroup(final Builder builder) { + name = builder.name; + description = builder.description; + members = builder.members; + removeMembers = builder.removeMembers; + admins = builder.admins; + removeAdmins = builder.removeAdmins; + resetGroupLink = builder.resetGroupLink; + groupLinkState = builder.groupLinkState; + addMemberPermission = builder.addMemberPermission; + editDetailsPermission = builder.editDetailsPermission; + avatarFile = builder.avatarFile; + expirationTimer = builder.expirationTimer; + isAnnouncementGroup = builder.isAnnouncementGroup; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(final UpdateGroup copy) { + Builder builder = new Builder(); + builder.name = copy.getName(); + builder.description = copy.getDescription(); + builder.members = copy.getMembers(); + builder.removeMembers = copy.getRemoveMembers(); + builder.admins = copy.getAdmins(); + builder.removeAdmins = copy.getRemoveAdmins(); + builder.resetGroupLink = copy.isResetGroupLink(); + builder.groupLinkState = copy.getGroupLinkState(); + builder.addMemberPermission = copy.getAddMemberPermission(); + builder.editDetailsPermission = copy.getEditDetailsPermission(); + builder.avatarFile = copy.getAvatarFile(); + builder.expirationTimer = copy.getExpirationTimer(); + builder.isAnnouncementGroup = copy.getIsAnnouncementGroup(); + return builder; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Set getMembers() { + return members; + } + + public Set getRemoveMembers() { + return removeMembers; + } + + public Set getAdmins() { + return admins; + } + + public Set getRemoveAdmins() { + return removeAdmins; + } + + public boolean isResetGroupLink() { + return resetGroupLink; + } + + public GroupLinkState getGroupLinkState() { + return groupLinkState; + } + + public GroupPermission getAddMemberPermission() { + return addMemberPermission; + } + + public GroupPermission getEditDetailsPermission() { + return editDetailsPermission; + } + + public File getAvatarFile() { + return avatarFile; + } + + public Integer getExpirationTimer() { + return expirationTimer; + } + + public Boolean getIsAnnouncementGroup() { + return isAnnouncementGroup; + } + + public static final class Builder { + + private String name; + private String description; + private Set members; + private Set removeMembers; + private Set admins; + private Set removeAdmins; + private boolean resetGroupLink; + private GroupLinkState groupLinkState; + private GroupPermission addMemberPermission; + private GroupPermission editDetailsPermission; + private File avatarFile; + private Integer expirationTimer; + private Boolean isAnnouncementGroup; + + private Builder() { + } + + public Builder withName(final String val) { + name = val; + return this; + } + + public Builder withDescription(final String val) { + description = val; + return this; + } + + public Builder withMembers(final Set val) { + members = val; + return this; + } + + public Builder withRemoveMembers(final Set val) { + removeMembers = val; + return this; + } + + public Builder withAdmins(final Set val) { + admins = val; + return this; + } + + public Builder withRemoveAdmins(final Set val) { + removeAdmins = val; + return this; + } + + public Builder withResetGroupLink(final boolean val) { + resetGroupLink = val; + return this; + } + + public Builder withGroupLinkState(final GroupLinkState val) { + groupLinkState = val; + return this; + } + + public Builder withAddMemberPermission(final GroupPermission val) { + addMemberPermission = val; + return this; + } + + public Builder withEditDetailsPermission(final GroupPermission val) { + editDetailsPermission = val; + return this; + } + + public Builder withAvatarFile(final File val) { + avatarFile = val; + return this; + } + + public Builder withExpirationTimer(final Integer val) { + expirationTimer = val; + return this; + } + + public Builder withIsAnnouncementGroup(final Boolean val) { + isAnnouncementGroup = val; + return this; + } + + public UpdateGroup build() { + return new UpdateGroup(this); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 62f4f111..ee2e9416 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -639,7 +639,7 @@ public class GroupHelper { return SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); + .withExpiration(g.getMessageExpirationTimer()); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { @@ -648,7 +648,7 @@ public class GroupHelper { .withSignedGroupChange(signedGroupChange); return SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); + .withExpiration(g.getMessageExpirationTimer()); } private SendGroupMessageResults sendUpdateGroupV2Message( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index c0953f1f..6c0fb2e9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -100,7 +100,7 @@ public class SendHelper { final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g ) throws IOException, GroupSendingNotAllowedException { GroupUtils.setGroupContext(messageBuilder, g); - messageBuilder.withExpiration(g.getMessageExpirationTime()); + messageBuilder.withExpiration(g.getMessageExpirationTimer()); final var message = messageBuilder.build(); final var recipients = g.getMembersWithout(account.getSelfRecipientId()); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java index 60efc84b..b4f4e63a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java @@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage.groups; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.storage.recipients.RecipientId; import java.util.Set; @@ -38,10 +39,16 @@ public abstract class GroupInfo { public abstract void setBlocked(boolean blocked); - public abstract int getMessageExpirationTime(); + public abstract int getMessageExpirationTimer(); public abstract boolean isAnnouncementGroup(); + public abstract GroupPermission getPermissionAddMember(); + + public abstract GroupPermission getPermissionEditDetails(); + + public abstract GroupPermission getPermissionSendMessage(); + public Set getMembersWithout(RecipientId recipientId) { return getMembers().stream().filter(member -> !member.equals(recipientId)).collect(Collectors.toSet()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java index 49c9a504..dbd2dcbb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java @@ -3,6 +3,7 @@ package org.asamk.signal.manager.storage.groups; import org.asamk.signal.manager.groups.GroupIdV1; import org.asamk.signal.manager.groups.GroupIdV2; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.storage.recipients.RecipientId; @@ -85,7 +86,7 @@ public class GroupInfoV1 extends GroupInfo { } @Override - public int getMessageExpirationTime() { + public int getMessageExpirationTimer() { return messageExpirationTime; } @@ -94,6 +95,21 @@ public class GroupInfoV1 extends GroupInfo { return false; } + @Override + public GroupPermission getPermissionAddMember() { + return GroupPermission.EVERY_MEMBER; + } + + @Override + public GroupPermission getPermissionEditDetails() { + return GroupPermission.EVERY_MEMBER; + } + + @Override + public GroupPermission getPermissionSendMessage() { + return GroupPermission.EVERY_MEMBER; + } + public void addMembers(Collection members) { this.members.addAll(members); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java index a06b83df..34db2a28 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java @@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage.groups; import org.asamk.signal.manager.groups.GroupIdV2; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientResolver; import org.signal.storageservice.protos.groups.AccessControl; @@ -151,7 +152,7 @@ public class GroupInfoV2 extends GroupInfo { } @Override - public int getMessageExpirationTime() { + public int getMessageExpirationTimer() { return this.group != null && this.group.hasDisappearingMessagesTimer() ? this.group.getDisappearingMessagesTimer().getDuration() : 0; @@ -162,6 +163,23 @@ public class GroupInfoV2 extends GroupInfo { return this.group != null && this.group.getIsAnnouncementGroup() == EnabledState.ENABLED; } + @Override + public GroupPermission getPermissionAddMember() { + final var accessControl = getAccessControl(); + return accessControl == null ? GroupPermission.EVERY_MEMBER : toGroupPermission(accessControl.getMembers()); + } + + @Override + public GroupPermission getPermissionEditDetails() { + final var accessControl = getAccessControl(); + return accessControl == null ? GroupPermission.EVERY_MEMBER : toGroupPermission(accessControl.getAttributes()); + } + + @Override + public GroupPermission getPermissionSendMessage() { + return isAnnouncementGroup() ? GroupPermission.ONLY_ADMINS : GroupPermission.EVERY_MEMBER; + } + public void setPermissionDenied(final boolean permissionDenied) { this.permissionDenied = permissionDenied; } @@ -169,4 +187,22 @@ public class GroupInfoV2 extends GroupInfo { public boolean isPermissionDenied() { return permissionDenied; } + + private AccessControl getAccessControl() { + if (this.group == null || !this.group.hasAccessControl()) { + return null; + } + + return this.group.getAccessControl(); + } + + private static GroupPermission toGroupPermission(final AccessControl.AccessRequired permission) { + switch (permission) { + case ADMINISTRATOR: + return GroupPermission.ONLY_ADMINS; + case MEMBER: + default: + return GroupPermission.EVERY_MEMBER; + } + } } diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index bf8265ff..349671b3 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -1,7 +1,6 @@ package org.asamk; import org.asamk.signal.commands.exceptions.IOErrorException; - import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.Struct; import org.freedesktop.dbus.annotations.DBusProperty; @@ -84,14 +83,27 @@ public interface Signal extends DBusInterface { void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber; + @Deprecated void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId; + @Deprecated List getGroupIds(); + DBusPath getGroup(byte[] groupId); + + List listGroups(); + + @Deprecated String getGroupName(byte[] groupId) throws Error.InvalidGroupId; + @Deprecated List getGroupMembers(byte[] groupId) throws Error.InvalidGroupId; + byte[] createGroup( + String name, List members, String avatar + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber; + + @Deprecated byte[] updateGroup( byte[] groupId, String name, List members, String avatar ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId; @@ -133,12 +145,15 @@ public interface Signal extends DBusInterface { List getContactNumber(final String name) throws Error.Failure; + @Deprecated void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidGroupId; boolean isContactBlocked(final String number) throws Error.InvalidNumber; + @Deprecated boolean isGroupBlocked(final byte[] groupId) throws Error.InvalidGroupId; + @Deprecated boolean isMember(final byte[] groupId) throws Error.InvalidGroupId; byte[] joinGroup(final String groupLink) throws Error.Failure; @@ -303,6 +318,71 @@ public interface Signal extends DBusInterface { void removeDevice() throws Error.Failure; } + class StructGroup extends Struct { + + @Position(0) + DBusPath objectPath; + + @Position(1) + byte[] id; + + @Position(2) + String name; + + public StructGroup(final DBusPath objectPath, final byte[] id, final String name) { + this.objectPath = objectPath; + this.id = id; + this.name = name; + } + + public DBusPath getObjectPath() { + return objectPath; + } + + public byte[] getId() { + return id; + } + + public String getName() { + return name; + } + } + + @DBusProperty(name = "Id", type = Byte[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Name", type = String.class) + @DBusProperty(name = "Description", type = String.class) + @DBusProperty(name = "Avatar", type = String.class, access = DBusProperty.Access.WRITE) + @DBusProperty(name = "IsBlocked", type = Boolean.class) + @DBusProperty(name = "IsMember", type = Boolean.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "IsAdmin", type = Boolean.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "MessageExpirationTimer", type = Integer.class) + @DBusProperty(name = "Members", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "PendingMembers", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "RequestingMembers", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Admins", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "PermissionAddMember", type = String.class) + @DBusProperty(name = "PermissionEditDetails", type = String.class) + @DBusProperty(name = "PermissionSendMessage", type = String.class) + @DBusProperty(name = "GroupInviteLink", type = String.class, access = DBusProperty.Access.READ) + interface Group extends DBusInterface, Properties { + + void quitGroup() throws Error.Failure, Error.LastGroupAdmin; + + void addMembers(List recipients) throws Error.Failure; + + void removeMembers(List recipients) throws Error.Failure; + + void addAdmins(List recipients) throws Error.Failure; + + void removeAdmins(List recipients) throws Error.Failure; + + void resetLink() throws Error.Failure; + + void disableLink() throws Error.Failure; + + void enableLink(boolean requiresApproval) throws Error.Failure; + } + interface Error { class AttachmentInvalid extends DBusExecutionException { @@ -347,6 +427,13 @@ public interface Signal extends DBusInterface { } } + class LastGroupAdmin extends DBusExecutionException { + + public LastGroupAdmin(final String message) { + super(message); + } + } + class InvalidNumber extends DBusExecutionException { public InvalidNumber(final String message) { diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index fd8c4b92..b2182429 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -63,7 +63,7 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { resolveMembers(group.getPendingMembers()), resolveMembers(group.getRequestingMembers()), resolveMembers(group.getAdminMembers()), - group.getMessageExpirationTime() == 0 ? "disabled" : group.getMessageExpirationTime() + "s", + group.getMessageExpirationTimer() == 0 ? "disabled" : group.getMessageExpirationTimer() + "s", groupInviteLink == null ? '-' : groupInviteLink.getUrl()); } else { writer.println("Id: {} Name: {} Active: {} Blocked: {}", @@ -91,11 +91,14 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { group.getDescription(), group.isMember(), group.isBlocked(), - group.getMessageExpirationTime(), + group.getMessageExpirationTimer(), resolveJsonMembers(group.getMembers()), resolveJsonMembers(group.getPendingMembers()), resolveJsonMembers(group.getRequestingMembers()), resolveJsonMembers(group.getAdminMembers()), + group.getPermissionAddMember().name(), + group.getPermissionEditDetails().name(), + group.getPermissionSendMessage().name(), groupInviteLink == null ? null : groupInviteLink.getUrl()); }).collect(Collectors.toList()); @@ -122,6 +125,9 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { public final Set pendingMembers; public final Set requestingMembers; public final Set admins; + public final String permissionAddMember; + public final String permissionEditDetails; + public final String permissionSendMessage; public final String groupInviteLink; public JsonGroup( @@ -135,6 +141,9 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { Set pendingMembers, Set requestingMembers, Set admins, + final String permissionAddMember, + final String permissionEditDetails, + final String permissionSendMessage, String groupInviteLink ) { this.id = id; @@ -148,6 +157,9 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { this.pendingMembers = pendingMembers; this.requestingMembers = requestingMembers; this.admins = admins; + this.permissionAddMember = permissionAddMember; + this.permissionEditDetails = permissionEditDetails; + this.permissionSendMessage = permissionSendMessage; this.groupInviteLink = groupInviteLink; } } diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 68bce2d2..b63a7160 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -12,6 +12,7 @@ import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupNotFoundException; @@ -145,21 +146,23 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { } var results = m.updateGroup(groupId, - groupName, - groupDescription, - groupMembers, - groupRemoveMembers, - groupAdmins, - groupRemoveAdmins, - groupResetLink, - groupLinkState, - groupAddMemberPermission, - groupEditDetailsPermission, - groupAvatar == null ? null : new File(groupAvatar), - groupExpiration, - groupSendMessagesPermission == null - ? null - : groupSendMessagesPermission == GroupPermission.ONLY_ADMINS); + UpdateGroup.newBuilder() + .withName(groupName) + .withDescription(groupDescription) + .withMembers(groupMembers) + .withRemoveMembers(groupRemoveMembers) + .withAdmins(groupAdmins) + .withRemoveAdmins(groupRemoveAdmins) + .withResetGroupLink(groupResetLink) + .withGroupLinkState(groupLinkState) + .withAddMemberPermission(groupAddMemberPermission) + .withEditDetailsPermission(groupEditDetailsPermission) + .withAvatarFile(groupAvatar == null ? null : new File(groupAvatar)) + .withExpirationTimer(groupExpiration) + .withIsAnnouncementGroup(groupSendMessagesPermission == null + ? null + : groupSendMessagesPermission == GroupPermission.ONLY_ADMINS) + .build()); if (results != null) { timestamp = results.getTimestamp(); ErrorUtils.handleSendMessageResults(results.getResults()); diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 53148c01..6e655bdc 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -15,9 +15,9 @@ import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; -import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; @@ -183,8 +183,8 @@ public class DbusManagerImpl implements Manager { @Override public List getGroups() { - final var groupIds = signal.getGroupIds(); - return groupIds.stream().map(id -> getGroup(GroupId.unknownVersion(id))).collect(Collectors.toList()); + final var groups = signal.listGroups(); + return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).collect(Collectors.toList()); } @Override @@ -194,7 +194,8 @@ public class DbusManagerImpl implements Manager { if (groupAdmins.size() > 0) { throw new UnsupportedOperationException(); } - signal.quitGroup(groupId.serialize()); + final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class); + group.quitGroup(); return new SendGroupMessageResults(0, List.of()); } @@ -207,8 +208,7 @@ public class DbusManagerImpl implements Manager { public Pair createGroup( final String name, final Set members, final File avatarFile ) throws IOException, AttachmentInvalidException { - final var newGroupId = signal.updateGroup(new byte[0], - emptyIfNull(name), + final var newGroupId = signal.createGroup(emptyIfNull(name), members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()), avatarFile == null ? "" : avatarFile.getPath()); return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of())); @@ -216,25 +216,76 @@ public class DbusManagerImpl implements Manager { @Override public SendGroupMessageResults updateGroup( - final GroupId groupId, - final String name, - final String description, - final Set members, - final Set removeMembers, - final Set admins, - final Set removeAdmins, - final boolean resetGroupLink, - final GroupLinkState groupLinkState, - final GroupPermission addMemberPermission, - final GroupPermission editDetailsPermission, - final File avatarFile, - final Integer expirationTimer, - final Boolean isAnnouncementGroup + final GroupId groupId, final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { - signal.updateGroup(groupId.serialize(), - emptyIfNull(name), - members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()), - avatarFile == null ? "" : avatarFile.getPath()); + final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class); + if (updateGroup.getName() != null) { + group.Set("org.asamk.Signal.Group", "Name", updateGroup.getName()); + } + if (updateGroup.getDescription() != null) { + group.Set("org.asamk.Signal.Group", "Description", updateGroup.getDescription()); + } + if (updateGroup.getAvatarFile() != null) { + group.Set("org.asamk.Signal.Group", + "Avatar", + updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile().getPath()); + } + if (updateGroup.getExpirationTimer() != null) { + group.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup.getExpirationTimer()); + } + if (updateGroup.getAddMemberPermission() != null) { + group.Set("org.asamk.Signal.Group", "PermissionAddMember", updateGroup.getAddMemberPermission().name()); + } + if (updateGroup.getEditDetailsPermission() != null) { + group.Set("org.asamk.Signal.Group", "PermissionEditDetails", updateGroup.getEditDetailsPermission().name()); + } + if (updateGroup.getIsAnnouncementGroup() != null) { + group.Set("org.asamk.Signal.Group", + "PermissionSendMessage", + updateGroup.getIsAnnouncementGroup() + ? GroupPermission.ONLY_ADMINS.name() + : GroupPermission.EVERY_MEMBER.name()); + } + if (updateGroup.getMembers() != null) { + group.addMembers(updateGroup.getMembers() + .stream() + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList())); + } + if (updateGroup.getRemoveMembers() != null) { + group.removeMembers(updateGroup.getRemoveMembers() + .stream() + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList())); + } + if (updateGroup.getAdmins() != null) { + group.addAdmins(updateGroup.getAdmins() + .stream() + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList())); + } + if (updateGroup.getRemoveAdmins() != null) { + group.removeAdmins(updateGroup.getRemoveAdmins() + .stream() + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList())); + } + if (updateGroup.isResetGroupLink()) { + group.resetLink(); + } + if (updateGroup.getGroupLinkState() != null) { + switch (updateGroup.getGroupLinkState()) { + case DISABLED: + group.disableLink(); + break; + case ENABLED: + group.enableLink(false); + break; + case ENABLED_WITH_APPROVAL: + group.enableLink(true); + break; + } + } return new SendGroupMessageResults(0, List.of()); } @@ -344,7 +395,12 @@ public class DbusManagerImpl implements Manager { public void setGroupBlocked( final GroupId groupId, final boolean blocked ) throws GroupNotFoundException, IOException { - signal.setGroupBlocked(groupId.serialize(), blocked); + setGroupProperty(groupId, "IsBlocked", blocked); + } + + private void setGroupProperty(final GroupId groupId, final String propertyName, final boolean blocked) { + final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class); + group.Set("org.asamk.Signal.Group", propertyName, blocked); } @Override @@ -411,19 +467,41 @@ public class DbusManagerImpl implements Manager { @Override public Group getGroup(final GroupId groupId) { - final var id = groupId.serialize(); - return new Group(groupId, - signal.getGroupName(id), - null, - null, - signal.getGroupMembers(id).stream().map(m -> new RecipientAddress(null, m)).collect(Collectors.toSet()), - Set.of(), - Set.of(), - Set.of(), - signal.isGroupBlocked(id), - 0, - false, - signal.isMember(id)); + final var groupPath = signal.getGroup(groupId.serialize()); + return getGroup(groupPath); + } + + @SuppressWarnings("unchecked") + private Group getGroup(final DBusPath groupPath) { + final var group = getRemoteObject(groupPath, Signal.Group.class).GetAll("org.asamk.Signal.Group"); + final var id = (byte[]) group.get("Id").getValue(); + try { + return new Group(GroupId.unknownVersion(id), + (String) group.get("Name").getValue(), + (String) group.get("Description").getValue(), + GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()), + ((List) group.get("Members").getValue()).stream() + .map(m -> new RecipientAddress(null, m)) + .collect(Collectors.toSet()), + ((List) group.get("PendingMembers").getValue()).stream() + .map(m -> new RecipientAddress(null, m)) + .collect(Collectors.toSet()), + ((List) group.get("RequestingMembers").getValue()).stream() + .map(m -> new RecipientAddress(null, m)) + .collect(Collectors.toSet()), + ((List) group.get("Admins").getValue()).stream() + .map(m -> new RecipientAddress(null, m)) + .collect(Collectors.toSet()), + (boolean) group.get("IsBlocked").getValue(), + (int) group.get("MessageExpirationTimer").getValue(), + GroupPermission.valueOf((String) group.get("PermissionAddMember").getValue()), + GroupPermission.valueOf((String) group.get("PermissionEditDetails").getValue()), + GroupPermission.valueOf((String) group.get("PermissionSendMessage").getValue()), + (boolean) group.get("IsMember").getValue(), + (boolean) group.get("IsAdmin").getValue()); + } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { + throw new AssertionError(e); + } } @Override diff --git a/src/main/java/org/asamk/signal/dbus/DbusProperty.java b/src/main/java/org/asamk/signal/dbus/DbusProperty.java index e0557786..5042458e 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusProperty.java +++ b/src/main/java/org/asamk/signal/dbus/DbusProperty.java @@ -21,6 +21,12 @@ public class DbusProperty { this.setter = null; } + public DbusProperty(final String name, final Consumer setter) { + this.name = name; + this.getter = null; + this.setter = setter; + } + public String getName() { return name; } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index ab19f0ce..56ecdc55 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -1,7 +1,6 @@ package org.asamk.signal.dbus; import org.asamk.Signal; -import org.asamk.Signal.Error; import org.asamk.signal.BaseConfig; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.manager.AttachmentInvalidException; @@ -13,9 +12,12 @@ import org.asamk.signal.manager.api.Identity; import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; @@ -26,6 +28,7 @@ import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.types.Variant; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; @@ -40,6 +43,8 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -58,6 +63,7 @@ public class DbusSignalImpl implements Signal { private DBusPath thisDevice; private final List devices = new ArrayList<>(); + private final List groups = new ArrayList<>(); public DbusSignalImpl(final Manager m, DBusConnection connection, final String objectPath) { this.m = m; @@ -67,6 +73,7 @@ public class DbusSignalImpl implements Signal { public void initObjects() { updateDevices(); + updateGroups(); } public void close() { @@ -415,6 +422,22 @@ public class DbusSignalImpl implements Signal { return ids; } + @Override + public DBusPath getGroup(final byte[] groupId) { + updateGroups(); + final var groupOptional = groups.stream().filter(g -> Arrays.equals(g.getId(), groupId)).findFirst(); + if (groupOptional.isEmpty()) { + throw new Error.GroupNotFound("Group not found"); + } + return groupOptional.get().getObjectPath(); + } + + @Override + public List listGroups() { + updateGroups(); + return groups; + } + @Override public String getGroupName(final byte[] groupId) { var group = m.getGroup(getGroupId(groupId)); @@ -431,10 +454,18 @@ public class DbusSignalImpl implements Signal { if (group == null) { return List.of(); } else { - return group.getMembers().stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList()); + final var members = group.getMembers(); + return getRecipientStrings(members); } } + @Override + public byte[] createGroup( + final String name, final List members, final String avatar + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber { + return updateGroup(new byte[0], name, members, avatar); + } + @Override public byte[] updateGroup(byte[] groupId, String name, List members, String avatar) { try { @@ -448,19 +479,11 @@ public class DbusSignalImpl implements Signal { return results.first().serialize(); } else { final var results = m.updateGroup(getGroupId(groupId), - name, - null, - memberIdentifiers, - null, - null, - null, - false, - null, - null, - null, - avatar == null ? null : new File(avatar), - null, - null); + UpdateGroup.newBuilder() + .withName(name) + .withMembers(memberIdentifiers) + .withAvatarFile(avatar == null ? null : new File(avatar)) + .build()); if (results != null) { checkSendMessageResults(results.getTimestamp(), results.getResults()); } @@ -740,6 +763,10 @@ public class DbusSignalImpl implements Signal { throw new Error.Failure(message.toString()); } + private static List getRecipientStrings(final Set members) { + return members.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList()); + } + private static Set getSingleRecipientIdentifiers( final Collection recipientStrings, final String localNumber ) throws DBusExecutionException { @@ -817,6 +844,38 @@ public class DbusSignalImpl implements Signal { this.devices.clear(); } + private static String getGroupObjectPath(String basePath, byte[] groupId) { + return basePath + "/Groups/" + Base64.getEncoder() + .encodeToString(groupId) + .replace("+", "_") + .replace("/", "_") + .replace("=", "_"); + } + + private void updateGroups() { + List groups; + groups = m.getGroups(); + + unExportGroups(); + + groups.forEach(g -> { + final var object = new DbusSignalGroupImpl(g.getGroupId()); + try { + connection.exportObject(object); + } catch (DBusException e) { + e.printStackTrace(); + } + this.groups.add(new StructGroup(new DBusPath(object.getObjectPath()), + g.getGroupId().serialize(), + emptyIfNull(g.getTitle()))); + }); + } + + private void unExportGroups() { + this.groups.stream().map(StructGroup::getObjectPath).map(DBusPath::getPath).forEach(connection::unExportObject); + this.groups.clear(); + } + public class DbusSignalDeviceImpl extends DbusProperties implements Signal.Device { private final org.asamk.signal.manager.api.Device device; @@ -858,4 +917,166 @@ public class DbusSignalImpl implements Signal { } } } + + public class DbusSignalGroupImpl extends DbusProperties implements Signal.Group { + + private final GroupId groupId; + + public DbusSignalGroupImpl(final GroupId groupId) { + this.groupId = groupId; + super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Group", + List.of(new DbusProperty<>("Id", groupId::serialize), + new DbusProperty<>("Name", () -> emptyIfNull(getGroup().getTitle()), this::setGroupName), + new DbusProperty<>("Description", + () -> emptyIfNull(getGroup().getDescription()), + this::setGroupDescription), + new DbusProperty<>("Avatar", this::setGroupAvatar), + new DbusProperty<>("IsBlocked", () -> getGroup().isBlocked(), this::setIsBlocked), + new DbusProperty<>("IsMember", () -> getGroup().isMember()), + new DbusProperty<>("IsAdmin", () -> getGroup().isAdmin()), + new DbusProperty<>("MessageExpirationTimer", + () -> getGroup().getMessageExpirationTimer(), + this::setMessageExpirationTime), + new DbusProperty<>("Members", + () -> new Variant<>(getRecipientStrings(getGroup().getMembers()), "as")), + new DbusProperty<>("PendingMembers", + () -> new Variant<>(getRecipientStrings(getGroup().getPendingMembers()), "as")), + new DbusProperty<>("RequestingMembers", + () -> new Variant<>(getRecipientStrings(getGroup().getRequestingMembers()), "as")), + new DbusProperty<>("Admins", + () -> new Variant<>(getRecipientStrings(getGroup().getAdminMembers()), "as")), + new DbusProperty<>("PermissionAddMember", + () -> getGroup().getPermissionAddMember().name(), + this::setGroupPermissionAddMember), + new DbusProperty<>("PermissionEditDetails", + () -> getGroup().getPermissionEditDetails().name(), + this::setGroupPermissionEditDetails), + new DbusProperty<>("PermissionSendMessage", + () -> getGroup().getPermissionSendMessage().name(), + this::setGroupPermissionSendMessage), + new DbusProperty<>("GroupInviteLink", () -> { + final var groupInviteLinkUrl = getGroup().getGroupInviteLinkUrl(); + return groupInviteLinkUrl == null ? "" : groupInviteLinkUrl.getUrl(); + })))); + } + + @Override + public String getObjectPath() { + return getGroupObjectPath(objectPath, groupId.serialize()); + } + + @Override + public void quitGroup() throws Error.Failure { + try { + m.quitGroup(groupId, Set.of()); + } catch (GroupNotFoundException | NotAGroupMemberException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (LastGroupAdminException e) { + throw new Error.LastGroupAdmin(e.getMessage()); + } + } + + @Override + public void addMembers(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withMembers(memberIdentifiers).build()); + } + + @Override + public void removeMembers(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withRemoveMembers(memberIdentifiers).build()); + } + + @Override + public void addAdmins(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withAdmins(memberIdentifiers).build()); + } + + @Override + public void removeAdmins(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withRemoveAdmins(memberIdentifiers).build()); + } + + @Override + public void resetLink() throws Error.Failure { + updateGroup(UpdateGroup.newBuilder().withResetGroupLink(true).build()); + } + + @Override + public void disableLink() throws Error.Failure { + updateGroup(UpdateGroup.newBuilder().withGroupLinkState(GroupLinkState.DISABLED).build()); + } + + @Override + public void enableLink(final boolean requiresApproval) throws Error.Failure { + updateGroup(UpdateGroup.newBuilder() + .withGroupLinkState(requiresApproval + ? GroupLinkState.ENABLED_WITH_APPROVAL + : GroupLinkState.ENABLED) + .build()); + } + + private org.asamk.signal.manager.api.Group getGroup() { + return m.getGroup(groupId); + } + + private void setGroupName(final String name) { + updateGroup(UpdateGroup.newBuilder().withName(name).build()); + } + + private void setGroupDescription(final String description) { + updateGroup(UpdateGroup.newBuilder().withDescription(description).build()); + } + + private void setGroupAvatar(final String avatar) { + updateGroup(UpdateGroup.newBuilder().withAvatarFile(new File(avatar)).build()); + } + + private void setMessageExpirationTime(final int expirationTime) { + updateGroup(UpdateGroup.newBuilder().withExpirationTimer(expirationTime).build()); + } + + private void setGroupPermissionAddMember(final String permission) { + updateGroup(UpdateGroup.newBuilder().withAddMemberPermission(GroupPermission.valueOf(permission)).build()); + } + + private void setGroupPermissionEditDetails(final String permission) { + updateGroup(UpdateGroup.newBuilder() + .withEditDetailsPermission(GroupPermission.valueOf(permission)) + .build()); + } + + private void setGroupPermissionSendMessage(final String permission) { + updateGroup(UpdateGroup.newBuilder() + .withIsAnnouncementGroup(GroupPermission.valueOf(permission) == GroupPermission.ONLY_ADMINS) + .build()); + } + + private void setIsBlocked(final boolean isBlocked) { + try { + m.setGroupBlocked(groupId, isBlocked); + } catch (GroupNotFoundException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } + } + + private void updateGroup(final UpdateGroup updateGroup) { + try { + m.updateGroup(groupId, updateGroup); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (AttachmentInvalidException e) { + throw new Error.AttachmentInvalid(e.getMessage()); + } + } + } }