Auto-generate profile images on conversations.

This commit is contained in:
Mikunj 2018-11-30 12:16:08 +11:00
parent 85fe666edb
commit 961eb53915
6 changed files with 103 additions and 39 deletions

67
app/profile_images.js Normal file
View File

@ -0,0 +1,67 @@
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const jdenticon = require('jdenticon');
// Icon config
jdenticon.config = {
lightness: {
color: [0.40, 0.80],
grayscale: [0.30, 0.90],
},
saturation: {
color: 0.50,
grayscale: 0.00,
},
backColor: '#86444400',
};
const { app } = require('electron').remote;
const userDataPath = app.getPath('userData');
const PATH = path.join(userDataPath, 'profileImages');
mkdirp.sync(PATH);
function hashCode(s) {
let h = 0;
for(let i = 0; i < s.length; i += 1)
h = Math.imul(31, h) + s.charCodeAt(i) | 0;
return h;
}
const hasImage = pubKey => fs.existsSync(getImagePath(pubKey));
const getImagePath = pubKey => `${PATH}/${pubKey}.png`;
const getOrCreateImagePath = pubKey => {
const imagePath = getImagePath(pubKey);
// If the image doesn't exist then create it
if (!hasImage(pubKey)) {
/*
We hash the pubKey and then pass that into jdenticon
This is because if we pass pubKey directly,
jdenticon trims the string and then generates a hash
meaning public keys with the same characters at the beginning
will get the same images
*/
const png = jdenticon.toPng(hashCode(pubKey), 50, 0.12);
fs.writeFileSync(imagePath, png);
}
return imagePath;
};
const removeImage = pubKey => {
if (hasImage(pubKey)) {
fs.unlinkSync(getImagePath(pubKey));
}
}
module.exports = {
getOrCreateImagePath,
getImagePath,
hasImage,
removeImage,
hashCode,
};

View File

@ -192,7 +192,7 @@
return conversation;
};
conversation.initialPromise = create();
conversation.initialPromise = create().then(() => conversation.updateProfileAvatar());
return conversation;
},
@ -251,12 +251,16 @@
conversations.add(collection.models);
this._initialFetchComplete = true;
await Promise.all(
conversations.map(conversation => conversation.updateLastMessage())
);
const promises = [];
conversations.forEach(conversation => {
promises.concat([
conversation.updateLastMessage(),
conversation.updateProfile(),
conversation.updateProfileAvatar(),
]);
});
await Promise.all(promises);
// Update profiles
conversations.map(conversation => conversation.updateProfile());
window.log.info('ConversationController: done with initial fetch');
} catch (error) {
window.log.error(

View File

@ -3,7 +3,7 @@
/* global BlockedNumberController: false */
/* global ConversationController: false */
/* global i18n: false */
/* global libsignal: false */
/* global profileImages: false */
/* global storage: false */
/* global textsecure: false */
/* global Whisper: false */
@ -174,6 +174,12 @@
deleteAttachmentData,
}
);
profileImages.removeImage(this.id);
},
async updateProfileAvatar() {
const path = profileImages.getOrCreateImagePath(this.id);
await this.setProfileAvatar(path);
},
async updateAndMerge(message) {
@ -1730,34 +1736,12 @@
}
},
async setProfileAvatar(avatarPath) {
if (!avatarPath) {
return;
}
const avatar = await textsecure.messaging.getAvatar(avatarPath);
const key = this.get('profileKey');
if (!key) {
return;
}
const keyBuffer = window.Signal.Crypto.base64ToArrayBuffer(key);
// decrypt
const decrypted = await textsecure.crypto.decryptProfile(
avatar,
keyBuffer
);
// update the conversation avatar only if hash differs
if (decrypted) {
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateProfileAvatar(
this.attributes,
decrypted,
{
writeNewAttachmentData,
deleteAttachmentData,
}
);
this.set(newAttributes);
const profileAvatar = this.get('profileAvatar');
if (profileAvatar !== avatarPath) {
this.set({ profileAvatar: avatarPath });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
}
},
async setProfileKey(profileKey) {
@ -1959,8 +1943,9 @@
getAvatarPath() {
const avatar = this.get('avatar') || this.get('profileAvatar');
if (avatar && avatar.path) {
return getAbsoluteAttachmentPath(avatar.path);
if (avatar) {
if (avatar.path) return getAbsoluteAttachmentPath(avatar.path);
return avatar;
}
return null;
@ -1970,8 +1955,10 @@
const color = this.getColor();
const avatar = this.get('avatar') || this.get('profileAvatar');
if (avatar && avatar.path) {
return { url: getAbsoluteAttachmentPath(avatar.path), color };
const url = avatar && avatar.path ? getAbsoluteAttachmentPath(avatar.path) : avatar;
if (url) {
return { url, color };
} else if (this.isPrivate()) {
const symbol = this.isValid() ? '#' : '!';
return {

View File

@ -334,6 +334,7 @@
openConversation(conversation) {
this.searchView.hideHints();
if (conversation) {
conversation.updateProfile();
ConversationController.markAsSelected(conversation);
this.conversation_stack.open(
ConversationController.get(conversation.id)

View File

@ -271,6 +271,7 @@ window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInst
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
window.loadImage = require('blueimp-load-image');
window.getGuid = require('uuid/v4');
window.profileImages = require('./app/profile_images');
window.React = require('react');
window.ReactDOM = require('react-dom');

View File

@ -1835,6 +1835,10 @@
&:hover {
background-color: $color-dark-70;
}
.module-avatar {
background-color: $color-dark-85;
}
}
.module-conversation-list-item--has-unread {