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