]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
e7aff6405ebd52fdebeda33bad7a5ec898352d09
[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 null);
92 }
93
94 return new Recipient(recipientId,
95 address,
96 contact,
97 profileKey,
98 expiringProfileKeyCredential,
99 profile,
100 null,
101 null);
102 }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
103
104 recipientStore.addLegacyRecipients(recipients);
105 } catch (FileNotFoundException e) {
106 // nothing to migrate
107 } catch (IOException e) {
108 logger.warn("Failed to load recipient store", e);
109 throw new RuntimeException(e);
110 }
111 try {
112 Files.delete(file.toPath());
113 } catch (IOException e) {
114 logger.warn("Failed to load recipient store", e);
115 throw new RuntimeException(e);
116 }
117 }
118
119 public record Storage(List<Recipient> recipients, long lastId) {
120
121 public record Recipient(
122 long id,
123 String number,
124 String uuid,
125 String profileKey,
126 String expiringProfileKeyCredential,
127 Contact contact,
128 Profile profile
129 ) {
130
131 public record Contact(
132 String name,
133 String color,
134 int messageExpirationTime,
135 boolean blocked,
136 boolean archived,
137 boolean profileSharingEnabled
138 ) {}
139
140 public record Profile(
141 long lastUpdateTimestamp,
142 String givenName,
143 String familyName,
144 String about,
145 String aboutEmoji,
146 String avatarUrlPath,
147 String mobileCoinAddress,
148 String unidentifiedAccessMode,
149 Set<String> capabilities
150 ) {}
151 }
152 }
153 }