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