mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Retrieve avatar profile image
This commit is contained in:
parent
6c4d272309
commit
0f3aa22519
4 changed files with 85 additions and 35 deletions
|
@ -142,6 +142,8 @@ import java.util.stream.Collectors;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import static org.asamk.signal.manager.ServiceConfig.capabilities;
|
||||
|
||||
public class Manager implements Closeable {
|
||||
|
||||
private final SleepTimer timer = new UptimeSleepTimer();
|
||||
|
@ -287,7 +289,7 @@ public class Manager implements Closeable {
|
|||
}
|
||||
|
||||
public void updateAccountAttributes() throws IOException {
|
||||
accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities, discoverableByPhoneNumber);
|
||||
accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
|
||||
}
|
||||
|
||||
public void setProfile(String name, File avatar) throws IOException {
|
||||
|
@ -372,7 +374,7 @@ public class Manager implements Closeable {
|
|||
verificationCode = verificationCode.replace("-", "");
|
||||
account.setSignalingKey(KeyUtils.createSignalingKey());
|
||||
// TODO make unrestricted unidentified access configurable
|
||||
VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities, discoverableByPhoneNumber);
|
||||
VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities, discoverableByPhoneNumber);
|
||||
|
||||
UUID uuid = UuidUtil.parseOrNull(response.getUuid());
|
||||
// TODO response.isStorageCapable()
|
||||
|
@ -440,7 +442,27 @@ public class Manager implements Closeable {
|
|||
}
|
||||
|
||||
private SignalProfile getRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
|
||||
return decryptProfile(getEncryptedRecipientProfile(address, unidentifiedAccess), profileKey);
|
||||
final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address, unidentifiedAccess);
|
||||
|
||||
File avatarFile = null;
|
||||
try {
|
||||
avatarFile = encryptedProfile.getAvatar() == null ? null : retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
|
||||
} catch (AssertionError e) {
|
||||
System.err.println("Failed to retrieve profile avatar: " + e.getMessage());
|
||||
}
|
||||
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
try {
|
||||
return new SignalProfile(
|
||||
encryptedProfile.getIdentityKey(),
|
||||
encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
|
||||
avatarFile,
|
||||
encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
|
||||
encryptedProfile.isUnrestrictedUnidentifiedAccess(),
|
||||
encryptedProfile.getCapabilities());
|
||||
} catch (InvalidCiphertextException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(byte[] groupId) throws IOException {
|
||||
|
@ -944,21 +966,6 @@ public class Manager implements Closeable {
|
|||
return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
|
||||
}
|
||||
|
||||
private static SignalProfile decryptProfile(SignalServiceProfile encryptedProfile, ProfileKey profileKey) throws IOException {
|
||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
||||
try {
|
||||
return new SignalProfile(
|
||||
encryptedProfile.getIdentityKey(),
|
||||
encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))),
|
||||
encryptedProfile.getAvatar(),
|
||||
encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(),
|
||||
encryptedProfile.isUnrestrictedUnidentifiedAccess()
|
||||
);
|
||||
} catch (InvalidCiphertextException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
|
||||
ContactInfo contact = account.getContactStore().getContact(recipient);
|
||||
if (contact == null || contact.profileKey == null) {
|
||||
|
@ -1718,6 +1725,29 @@ public class Manager implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
private File getProfileAvatarFile(SignalServiceAddress address) {
|
||||
return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier());
|
||||
}
|
||||
|
||||
private File retrieveProfileAvatar(SignalServiceAddress address, String avatarPath, ProfileKey profileKey) throws IOException {
|
||||
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
|
||||
SignalServiceMessageReceiver receiver = getMessageReceiver();
|
||||
File outputFile = getProfileAvatarFile(address);
|
||||
|
||||
File tmpFile = IOUtils.createTempFile();
|
||||
try (InputStream input = receiver.retrieveProfileAvatar(avatarPath, tmpFile, profileKey, ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
|
||||
// Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
|
||||
IOUtils.copyStreamToFile(input, outputFile, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
|
||||
} finally {
|
||||
try {
|
||||
Files.delete(tmpFile.toPath());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
|
||||
return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
|
||||
}
|
||||
|
@ -1743,17 +1773,7 @@ public class Manager implements Closeable {
|
|||
|
||||
File tmpFile = IOUtils.createTempFile();
|
||||
try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE)) {
|
||||
try (OutputStream output = new FileOutputStream(outputFile)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
while ((read = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
IOUtils.copyStreamToFile(input, outputFile);
|
||||
} finally {
|
||||
try {
|
||||
Files.delete(tmpFile.toPath());
|
||||
|
|
|
@ -26,6 +26,7 @@ public class ServiceConfig {
|
|||
final static int PREKEY_MINIMUM_COUNT = 20;
|
||||
final static int PREKEY_BATCH_SIZE = 100;
|
||||
final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
|
||||
final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
private final static String URL = "https://textsecure-service.whispersystems.org";
|
||||
private final static String CDN_URL = "https://cdn.signal.org";
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
package org.asamk.signal.manager;
|
||||
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SignalProfile {
|
||||
|
||||
private final String identityKey;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String avatar;
|
||||
private final File avatarFile;
|
||||
|
||||
private final String unidentifiedAccess;
|
||||
|
||||
private final boolean unrestrictedUnidentifiedAccess;
|
||||
|
||||
public SignalProfile(final String identityKey, final String name, final String avatar, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess) {
|
||||
private final SignalServiceProfile.Capabilities capabilities;
|
||||
|
||||
public SignalProfile(final String identityKey, final String name, final File avatarFile, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final SignalServiceProfile.Capabilities capabilities) {
|
||||
this.identityKey = identityKey;
|
||||
this.name = name;
|
||||
this.avatar = avatar;
|
||||
this.avatarFile = avatarFile;
|
||||
this.unidentifiedAccess = unidentifiedAccess;
|
||||
this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public String getIdentityKey() {
|
||||
|
@ -28,8 +35,8 @@ public class SignalProfile {
|
|||
return name;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
public File getAvatarFile() {
|
||||
return avatarFile;
|
||||
}
|
||||
|
||||
public String getUnidentifiedAccess() {
|
||||
|
@ -40,14 +47,19 @@ public class SignalProfile {
|
|||
return unrestrictedUnidentifiedAccess;
|
||||
}
|
||||
|
||||
public SignalServiceProfile.Capabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SignalProfile{" +
|
||||
"identityKey='" + identityKey + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", avatar='" + avatar + '\'' +
|
||||
", avatarFile=" + avatarFile +
|
||||
", unidentifiedAccess='" + unidentifiedAccess + '\'' +
|
||||
", unrestrictedUnidentifiedAccess=" + unrestrictedUnidentifiedAccess +
|
||||
", capabilities=" + capabilities +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ import org.whispersystems.signalservice.internal.util.Util;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
|
@ -77,4 +79,19 @@ public class IOUtils {
|
|||
|
||||
return System.getProperty("user.home") + "/.local/share";
|
||||
}
|
||||
|
||||
public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
|
||||
copyStreamToFile(input, outputFile, 8192);
|
||||
}
|
||||
|
||||
public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
|
||||
try (OutputStream output = new FileOutputStream(outputFile)) {
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int read;
|
||||
|
||||
while ((read = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue