]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
55231cb7eb474dfcc568007f45ccc6c740b19bd8
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / recipients / LegacyRecipientStore2.java
1 package org.asamk.signal.manager.storage.recipients;
2
3 import org.asamk.signal.manager.api.Contact;
4 import org.asamk.signal.manager.api.Profile;
5 import org.asamk.signal.manager.storage.Utils;
6 import org.signal.libsignal.zkgroup.InvalidInputException;
7 import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
8 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.whispersystems.signalservice.api.push.ServiceId;
12
13 import java.io.File;
14 import java.io.FileInputStream;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.nio.file.Files;
18 import java.util.Base64;
19 import java.util.List;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.stream.Collectors;
24
25 public class LegacyRecipientStore2 {
26
27 private static final Logger logger = LoggerFactory.getLogger(LegacyRecipientStore2.class);
28
29 public static void migrate(File file, RecipientStore recipientStore) {
30 final var objectMapper = Utils.createStorageObjectMapper();
31 try (var inputStream = new FileInputStream(file)) {
32 final var storage = objectMapper.readValue(inputStream, Storage.class);
33
34 final var recipients = storage.recipients.stream().map(r -> {
35 final var recipientId = new RecipientId(r.id, recipientStore);
36 final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(ServiceId::parseOrThrow),
37 Optional.ofNullable(r.number));
38
39 Contact contact = null;
40 if (r.contact != null) {
41 contact = new Contact(r.contact.name,
42 null,
43 r.contact.color,
44 r.contact.messageExpirationTime,
45 r.contact.blocked,
46 r.contact.archived,
47 r.contact.profileSharingEnabled,
48 false);
49 }
50
51 ProfileKey profileKey = null;
52 if (r.profileKey != null) {
53 try {
54 profileKey = new ProfileKey(Base64.getDecoder().decode(r.profileKey));
55 } catch (InvalidInputException ignored) {
56 }
57 }
58
59 ExpiringProfileKeyCredential expiringProfileKeyCredential = null;
60 if (r.expiringProfileKeyCredential != null) {
61 try {
62 expiringProfileKeyCredential = new ExpiringProfileKeyCredential(Base64.getDecoder()
63 .decode(r.expiringProfileKeyCredential));
64 } catch (Throwable ignored) {
65 }
66 }
67
68 Profile profile = null;
69 if (r.profile != null) {
70 profile = new Profile(r.profile.lastUpdateTimestamp,
71 r.profile.givenName,
72 r.profile.familyName,
73 r.profile.about,
74 r.profile.aboutEmoji,
75 r.profile.avatarUrlPath,
76 r.profile.mobileCoinAddress == null
77 ? null
78 : Base64.getDecoder().decode(r.profile.mobileCoinAddress),
79 Profile.UnidentifiedAccessMode.valueOfOrUnknown(r.profile.unidentifiedAccessMode),
80 r.profile.capabilities.stream()
81 .map(Profile.Capability::valueOfOrNull)
82 .filter(Objects::nonNull)
83 .collect(Collectors.toSet()));
84 }
85
86 return new Recipient(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
87 }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
88
89 recipientStore.addLegacyRecipients(recipients);
90 } catch (FileNotFoundException e) {
91 // nothing to migrate
92 } catch (IOException e) {
93 logger.warn("Failed to load recipient store", e);
94 throw new RuntimeException(e);
95 }
96 try {
97 Files.delete(file.toPath());
98 } catch (IOException e) {
99 logger.warn("Failed to load recipient store", e);
100 throw new RuntimeException(e);
101 }
102 }
103
104 public record Storage(List<Recipient> recipients, long lastId) {
105
106 public record Recipient(
107 long id,
108 String number,
109 String uuid,
110 String profileKey,
111 String expiringProfileKeyCredential,
112 Contact contact,
113 Profile profile
114 ) {
115
116 public record Contact(
117 String name,
118 String color,
119 int messageExpirationTime,
120 boolean blocked,
121 boolean archived,
122 boolean profileSharingEnabled
123 ) {}
124
125 public record Profile(
126 long lastUpdateTimestamp,
127 String givenName,
128 String familyName,
129 String about,
130 String aboutEmoji,
131 String avatarUrlPath,
132 String mobileCoinAddress,
133 String unidentifiedAccessMode,
134 Set<String> capabilities
135 ) {}
136 }
137 }
138 }