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