]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
186ac517773bbbceb38285a59ba8f04cab5cbc73
[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 null,
44 null,
45 null,
46 null,
47 r.contact.color,
48 r.contact.messageExpirationTime,
49 0,
50 false,
51 r.contact.blocked,
52 r.contact.archived,
53 r.contact.profileSharingEnabled,
54 false,
55 null);
56 }
57
58 ProfileKey profileKey = null;
59 if (r.profileKey != null) {
60 try {
61 profileKey = new ProfileKey(Base64.getDecoder().decode(r.profileKey));
62 } catch (InvalidInputException ignored) {
63 }
64 }
65
66 ExpiringProfileKeyCredential expiringProfileKeyCredential = null;
67 if (r.expiringProfileKeyCredential != null) {
68 try {
69 expiringProfileKeyCredential = new ExpiringProfileKeyCredential(Base64.getDecoder()
70 .decode(r.expiringProfileKeyCredential));
71 } catch (Throwable ignored) {
72 }
73 }
74
75 Profile profile = null;
76 if (r.profile != null) {
77 profile = new Profile(r.profile.lastUpdateTimestamp,
78 r.profile.givenName,
79 r.profile.familyName,
80 r.profile.about,
81 r.profile.aboutEmoji,
82 r.profile.avatarUrlPath,
83 r.profile.mobileCoinAddress == null
84 ? null
85 : Base64.getDecoder().decode(r.profile.mobileCoinAddress),
86 Profile.UnidentifiedAccessMode.valueOfOrUnknown(r.profile.unidentifiedAccessMode),
87 r.profile.capabilities.stream()
88 .map(Profile.Capability::valueOfOrNull)
89 .filter(Objects::nonNull)
90 .collect(Collectors.toSet()));
91 }
92
93 return new Recipient(recipientId,
94 address,
95 contact,
96 profileKey,
97 expiringProfileKeyCredential,
98 profile,
99 null);
100 }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
101
102 recipientStore.addLegacyRecipients(recipients);
103 } catch (FileNotFoundException e) {
104 // nothing to migrate
105 } catch (IOException e) {
106 logger.warn("Failed to load recipient store", e);
107 throw new RuntimeException(e);
108 }
109 try {
110 Files.delete(file.toPath());
111 } catch (IOException e) {
112 logger.warn("Failed to load recipient store", e);
113 throw new RuntimeException(e);
114 }
115 }
116
117 public record Storage(List<Recipient> recipients, long lastId) {
118
119 public record Recipient(
120 long id,
121 String number,
122 String uuid,
123 String profileKey,
124 String expiringProfileKeyCredential,
125 Contact contact,
126 Profile profile
127 ) {
128
129 public record Contact(
130 String name,
131 String color,
132 int messageExpirationTime,
133 boolean blocked,
134 boolean archived,
135 boolean profileSharingEnabled
136 ) {}
137
138 public record Profile(
139 long lastUpdateTimestamp,
140 String givenName,
141 String familyName,
142 String about,
143 String aboutEmoji,
144 String avatarUrlPath,
145 String mobileCoinAddress,
146 String unidentifiedAccessMode,
147 Set<String> capabilities
148 ) {}
149 }
150 }
151 }