]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Reformat
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusSignalImpl.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.Signal;
4 import org.asamk.signal.BaseConfig;
5 import org.asamk.signal.manager.AttachmentInvalidException;
6 import org.asamk.signal.manager.Manager;
7 import org.asamk.signal.manager.groups.GroupId;
8 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
9 import org.asamk.signal.manager.groups.GroupNotFoundException;
10 import org.asamk.signal.manager.groups.NotAGroupMemberException;
11 import org.asamk.signal.manager.storage.protocol.IdentityInfo;
12 import org.asamk.signal.manager.util.Utils;
13 import org.asamk.signal.util.ErrorUtils;
14 import org.freedesktop.dbus.exceptions.DBusExecutionException;
15 import org.whispersystems.libsignal.util.guava.Optional;
16 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
17 import org.whispersystems.signalservice.api.messages.SendMessageResult;
18 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
19 import org.whispersystems.signalservice.api.util.InvalidNumberException;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28
29 public class DbusSignalImpl implements Signal {
30
31 private final Manager m;
32
33 public DbusSignalImpl(final Manager m) {
34 this.m = m;
35 }
36
37 @Override
38 public boolean isRemote() {
39 return false;
40 }
41
42 @Override
43 public String getObjectPath() {
44 return null;
45 }
46
47 @Override
48 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
49 var recipients = new ArrayList<String>(1);
50 recipients.add(recipient);
51 return sendMessage(message, attachments, recipients);
52 }
53
54 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
55 var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
56
57 if (error == null) {
58 return;
59 }
60
61 final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
62
63 if (result.getIdentityFailure() != null) {
64 throw new Error.UntrustedIdentity(message);
65 } else {
66 throw new Error.Failure(message);
67 }
68 }
69
70 private static void checkSendMessageResults(
71 long timestamp, List<SendMessageResult> results
72 ) throws DBusExecutionException {
73 if (results.size() == 1) {
74 checkSendMessageResult(timestamp, results.get(0));
75 return;
76 }
77
78 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
79 if (errors.size() == 0) {
80 return;
81 }
82
83 var message = new StringBuilder();
84 message.append(timestamp).append('\n');
85 message.append("Failed to send (some) messages:\n");
86 for (var error : errors) {
87 message.append(error).append('\n');
88 }
89
90 throw new Error.Failure(message.toString());
91 }
92
93 @Override
94 public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
95 try {
96 final var results = m.sendMessage(message, attachments, recipients);
97 checkSendMessageResults(results.first(), results.second());
98 return results.first();
99 } catch (InvalidNumberException e) {
100 throw new Error.InvalidNumber(e.getMessage());
101 } catch (AttachmentInvalidException e) {
102 throw new Error.AttachmentInvalid(e.getMessage());
103 } catch (IOException e) {
104 throw new Error.Failure(e.getMessage());
105 }
106 }
107
108 @Override
109 public long sendNoteToSelfMessage(
110 final String message, final List<String> attachments
111 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
112 try {
113 final var results = m.sendSelfMessage(message, attachments);
114 checkSendMessageResult(results.first(), results.second());
115 return results.first();
116 } catch (AttachmentInvalidException e) {
117 throw new Error.AttachmentInvalid(e.getMessage());
118 } catch (IOException e) {
119 throw new Error.Failure(e.getMessage());
120 }
121 }
122
123 @Override
124 public void sendEndSessionMessage(final List<String> recipients) {
125 try {
126 final var results = m.sendEndSessionMessage(recipients);
127 checkSendMessageResults(results.first(), results.second());
128 } catch (IOException e) {
129 throw new Error.Failure(e.getMessage());
130 } catch (InvalidNumberException e) {
131 throw new Error.InvalidNumber(e.getMessage());
132 }
133 }
134
135 @Override
136 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
137 try {
138 var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId));
139 checkSendMessageResults(results.first(), results.second());
140 return results.first();
141 } catch (IOException e) {
142 throw new Error.Failure(e.getMessage());
143 } catch (GroupNotFoundException | NotAGroupMemberException e) {
144 throw new Error.GroupNotFound(e.getMessage());
145 } catch (AttachmentInvalidException e) {
146 throw new Error.AttachmentInvalid(e.getMessage());
147 }
148 }
149
150 // Since contact names might be empty if not defined, also potentially return
151 // the profile name
152 @Override
153 public String getContactName(final String number) {
154 String name = "";
155 try {
156 name = m.getContactOrProfileName(number);
157 } catch (Exception e) {
158 throw new Error.InvalidNumber(e.getMessage());
159 }
160 return name;
161 }
162
163 @Override
164 public void setContactName(final String number, final String name) {
165 try {
166 m.setContactName(number, name);
167 } catch (InvalidNumberException e) {
168 throw new Error.InvalidNumber(e.getMessage());
169 }
170 }
171
172 @Override
173 public void setContactBlocked(final String number, final boolean blocked) {
174 try {
175 m.setContactBlocked(number, blocked);
176 } catch (InvalidNumberException e) {
177 throw new Error.InvalidNumber(e.getMessage());
178 }
179 }
180
181 @Override
182 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
183 try {
184 m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
185 } catch (GroupNotFoundException e) {
186 throw new Error.GroupNotFound(e.getMessage());
187 }
188 }
189
190 @Override
191 public List<byte[]> getGroupIds() {
192 var groups = m.getGroups();
193 var ids = new ArrayList<byte[]>(groups.size());
194 for (var group : groups) {
195 ids.add(group.getGroupId().serialize());
196 }
197 return ids;
198 }
199
200 @Override
201 public String getGroupName(final byte[] groupId) {
202 var group = m.getGroup(GroupId.unknownVersion(groupId));
203 if (group == null) {
204 return "";
205 } else {
206 return group.getTitle();
207 }
208 }
209
210 @Override
211 public List<String> getGroupMembers(final byte[] groupId) {
212 var group = m.getGroup(GroupId.unknownVersion(groupId));
213 if (group == null) {
214 return List.of();
215 } else {
216 return group.getMembers()
217 .stream()
218 .map(m::resolveSignalServiceAddress)
219 .map(SignalServiceAddress::getLegacyIdentifier)
220 .collect(Collectors.toList());
221 }
222 }
223
224 @Override
225 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
226 try {
227 if (groupId.length == 0) {
228 groupId = null;
229 }
230 if (name.isEmpty()) {
231 name = null;
232 }
233 if (members.isEmpty()) {
234 members = null;
235 }
236 if (avatar.isEmpty()) {
237 avatar = null;
238 }
239 final var results = m.updateGroup(groupId == null ? null : GroupId.unknownVersion(groupId),
240 name,
241 members,
242 avatar == null ? null : new File(avatar));
243 checkSendMessageResults(0, results.second());
244 return results.first().serialize();
245 } catch (IOException e) {
246 throw new Error.Failure(e.getMessage());
247 } catch (GroupNotFoundException | NotAGroupMemberException e) {
248 throw new Error.GroupNotFound(e.getMessage());
249 } catch (InvalidNumberException e) {
250 throw new Error.InvalidNumber(e.getMessage());
251 } catch (AttachmentInvalidException e) {
252 throw new Error.AttachmentInvalid(e.getMessage());
253 }
254 }
255
256 @Override
257 public boolean isRegistered() {
258 return true;
259 }
260
261 @Override
262 public void updateProfile(
263 final String name,
264 final String about,
265 final String aboutEmoji,
266 String avatarPath,
267 final boolean removeAvatar
268 ) {
269 try {
270 if (avatarPath.isEmpty()) {
271 avatarPath = null;
272 }
273 Optional<File> avatarFile = removeAvatar
274 ? Optional.absent()
275 : avatarPath == null ? null : Optional.of(new File(avatarPath));
276 m.setProfile(name, about, aboutEmoji, avatarFile);
277 } catch (IOException e) {
278 throw new Error.Failure(e.getMessage());
279 }
280 }
281
282 // Provide option to query a version string in order to react on potential
283 // future interface changes
284 @Override
285 public String version() {
286 return BaseConfig.PROJECT_VERSION;
287 }
288
289 // Create a unique list of Numbers from Identities and Contacts to really get
290 // all numbers the system knows
291 @Override
292 public List<String> listNumbers() {
293 return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()),
294 m.getContacts().stream().map(c -> c.number))
295 .filter(Objects::nonNull)
296 .distinct()
297 .collect(Collectors.toList());
298 }
299
300 @Override
301 public List<String> getContactNumber(final String name) {
302 // Contact names have precendence.
303 List<String> numbers = new ArrayList<>();
304 var contacts = m.getContacts();
305 for (var c : contacts) {
306 if (c.name != null && c.name.equals(name)) {
307 numbers.add(c.number);
308 }
309 }
310 // Try profiles if no contact name was found
311 for (IdentityInfo identity : m.getIdentities()) {
312 String number = identity.getAddress().getNumber().orNull();
313 if (number != null) {
314 var address = Utils.getSignalServiceAddressFromIdentifier(number);
315 var profile = m.getRecipientProfile(address);
316 String profileName = profile.getDisplayName();
317 if (profileName.equals(name)) {
318 numbers.add(number);
319 }
320 }
321 }
322 if (numbers.size() == 0) {
323 throw new Error.Failure("Contact name not found");
324 }
325 return numbers;
326 }
327
328 @Override
329 public void quitGroup(final byte[] groupId) {
330 var group = GroupId.unknownVersion(groupId);
331 try {
332 m.sendQuitGroupMessage(group);
333 } catch (GroupNotFoundException | NotAGroupMemberException e) {
334 throw new Error.GroupNotFound(e.getMessage());
335 } catch (IOException e) {
336 throw new Error.Failure(e.getMessage());
337 }
338 }
339
340 @Override
341 public void joinGroup(final String groupLink) {
342 final GroupInviteLinkUrl linkUrl;
343 try {
344 linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
345 m.joinGroup(linkUrl);
346 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
347 throw new Error.Failure("Group link is invalid: " + e.getMessage());
348 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
349 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
350 } catch (IOException e) {
351 throw new Error.Failure(e.getMessage());
352 }
353 }
354
355 @Override
356 public boolean isContactBlocked(final String number) {
357 var contacts = m.getContacts();
358 for (var c : contacts) {
359 if (c.number.equals(number)) {
360 return c.blocked;
361 }
362 }
363 return false;
364 }
365
366 @Override
367 public boolean isGroupBlocked(final byte[] groupId) {
368 var group = m.getGroup(GroupId.unknownVersion(groupId));
369 if (group == null) {
370 return false;
371 } else {
372 return group.isBlocked();
373 }
374 }
375
376 @Override
377 public boolean isMember(final byte[] groupId) {
378 var group = m.getGroup(GroupId.unknownVersion(groupId));
379 if (group == null) {
380 return false;
381 } else {
382 return group.isMember(m.getSelfAddress());
383 }
384 }
385
386 }