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;
) throws IOException, AttachmentInvalidException;
SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> 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<GroupId, SendGroupMessageResults> joinGroup(
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;
.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
@Override
public SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> 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
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;
private final Set<RecipientAddress> requestingMembers;
private final Set<RecipientAddress> 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,
final Set<RecipientAddress> requestingMembers,
final Set<RecipientAddress> 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;
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() {
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;
+ }
}
--- /dev/null
+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<RecipientIdentifier.Single> members;
+ private final Set<RecipientIdentifier.Single> removeMembers;
+ private final Set<RecipientIdentifier.Single> admins;
+ private final Set<RecipientIdentifier.Single> 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<RecipientIdentifier.Single> getMembers() {
+ return members;
+ }
+
+ public Set<RecipientIdentifier.Single> getRemoveMembers() {
+ return removeMembers;
+ }
+
+ public Set<RecipientIdentifier.Single> getAdmins() {
+ return admins;
+ }
+
+ public Set<RecipientIdentifier.Single> 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<RecipientIdentifier.Single> members;
+ private Set<RecipientIdentifier.Single> removeMembers;
+ private Set<RecipientIdentifier.Single> admins;
+ private Set<RecipientIdentifier.Single> 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<RecipientIdentifier.Single> val) {
+ members = val;
+ return this;
+ }
+
+ public Builder withRemoveMembers(final Set<RecipientIdentifier.Single> val) {
+ removeMembers = val;
+ return this;
+ }
+
+ public Builder withAdmins(final Set<RecipientIdentifier.Single> val) {
+ admins = val;
+ return this;
+ }
+
+ public Builder withRemoveAdmins(final Set<RecipientIdentifier.Single> 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);
+ }
+ }
+}
return SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build())
- .withExpiration(g.getMessageExpirationTime());
+ .withExpiration(g.getMessageExpirationTimer());
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
.withSignedGroupChange(signedGroupChange);
return SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build())
- .withExpiration(g.getMessageExpirationTime());
+ .withExpiration(g.getMessageExpirationTimer());
}
private SendGroupMessageResults sendUpdateGroupV2Message(
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());
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;
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<RecipientId> getMembersWithout(RecipientId recipientId) {
return getMembers().stream().filter(member -> !member.equals(recipientId)).collect(Collectors.toSet());
}
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;
}
@Override
- public int getMessageExpirationTime() {
+ public int getMessageExpirationTimer() {
return messageExpirationTime;
}
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<RecipientId> members) {
this.members.addAll(members);
}
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;
}
@Override
- public int getMessageExpirationTime() {
+ public int getMessageExpirationTimer() {
return this.group != null && this.group.hasDisappearingMessagesTimer()
? this.group.getDisappearingMessagesTimer().getDuration()
: 0;
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;
}
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;
+ }
+ }
}
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;
void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber;
+ @Deprecated
void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId;
+ @Deprecated
List<byte[]> getGroupIds();
+ DBusPath getGroup(byte[] groupId);
+
+ List<StructGroup> listGroups();
+
+ @Deprecated
String getGroupName(byte[] groupId) throws Error.InvalidGroupId;
+ @Deprecated
List<String> getGroupMembers(byte[] groupId) throws Error.InvalidGroupId;
+ byte[] createGroup(
+ String name, List<String> members, String avatar
+ ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber;
+
+ @Deprecated
byte[] updateGroup(
byte[] groupId, String name, List<String> members, String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
List<String> 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;
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<String> recipients) throws Error.Failure;
+
+ void removeMembers(List<String> recipients) throws Error.Failure;
+
+ void addAdmins(List<String> recipients) throws Error.Failure;
+
+ void removeAdmins(List<String> 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 {
}
}
+ class LastGroupAdmin extends DBusExecutionException {
+
+ public LastGroupAdmin(final String message) {
+ super(message);
+ }
+ }
+
class InvalidNumber extends DBusExecutionException {
public InvalidNumber(final String message) {
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: {}",
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());
public final Set<JsonGroupMember> pendingMembers;
public final Set<JsonGroupMember> requestingMembers;
public final Set<JsonGroupMember> admins;
+ public final String permissionAddMember;
+ public final String permissionEditDetails;
+ public final String permissionSendMessage;
public final String groupInviteLink;
public JsonGroup(
Set<JsonGroupMember> pendingMembers,
Set<JsonGroupMember> requestingMembers,
Set<JsonGroupMember> admins,
+ final String permissionAddMember,
+ final String permissionEditDetails,
+ final String permissionSendMessage,
String groupInviteLink
) {
this.id = id;
this.pendingMembers = pendingMembers;
this.requestingMembers = requestingMembers;
this.admins = admins;
+ this.permissionAddMember = permissionAddMember;
+ this.permissionEditDetails = permissionEditDetails;
+ this.permissionSendMessage = permissionSendMessage;
this.groupInviteLink = groupInviteLink;
}
}
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;
}
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());
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;
@Override
public List<Group> 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
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());
}
public Pair<GroupId, SendGroupMessageResults> createGroup(
final String name, final Set<RecipientIdentifier.Single> 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()));
@Override
public SendGroupMessageResults updateGroup(
- final GroupId groupId,
- final String name,
- final String description,
- final Set<RecipientIdentifier.Single> members,
- final Set<RecipientIdentifier.Single> removeMembers,
- final Set<RecipientIdentifier.Single> admins,
- final Set<RecipientIdentifier.Single> 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());
}
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
@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<String>) group.get("Members").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) group.get("PendingMembers").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) group.get("RequestingMembers").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) 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
this.setter = null;
}
+ public DbusProperty(final String name, final Consumer<T> setter) {
+ this.name = name;
+ this.getter = null;
+ this.setter = setter;
+ }
+
public String getName() {
return name;
}
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;
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;
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;
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;
private DBusPath thisDevice;
private final List<StructDevice> devices = new ArrayList<>();
+ private final List<StructGroup> groups = new ArrayList<>();
public DbusSignalImpl(final Manager m, DBusConnection connection, final String objectPath) {
this.m = m;
public void initObjects() {
updateDevices();
+ updateGroups();
}
public void close() {
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<StructGroup> listGroups() {
+ updateGroups();
+ return groups;
+ }
+
@Override
public String getGroupName(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
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<String> 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<String> members, String avatar) {
try {
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());
}
throw new Error.Failure(message.toString());
}
+ private static List<String> getRecipientStrings(final Set<RecipientAddress> members) {
+ return members.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList());
+ }
+
private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
final Collection<String> recipientStrings, final String localNumber
) throws DBusExecutionException {
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<org.asamk.signal.manager.api.Group> 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;
}
}
}
+
+ 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<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withMembers(memberIdentifiers).build());
+ }
+
+ @Override
+ public void removeMembers(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withRemoveMembers(memberIdentifiers).build());
+ }
+
+ @Override
+ public void addAdmins(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withAdmins(memberIdentifiers).build());
+ }
+
+ @Override
+ public void removeAdmins(final List<String> 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());
+ }
+ }
+ }
}