Avatars plugin added other identicon types (needs config settings)

This commit is contained in:
the-djmaze 2022-11-30 17:03:07 +01:00
parent 9df052a435
commit 9502fa3f5a
4 changed files with 97 additions and 23 deletions

View file

@ -11,6 +11,21 @@
return `${bimi}/${from.email.toLowerCase()}`;
},
getAvatar = msg => ncAvatars.get(msg.from[0].email.toLowerCase()) || avatars.get(getAvatarUid(msg)),
hash = async txt => {
if (/^[0-9a-f]{15,}$/i.test(txt)) {
return txt;
}
const hashArray = Array.from(new Uint8Array(
// await crypto.subtle.digest('SHA-256', (new TextEncoder()).encode(txt.toLowerCase()))
await crypto.subtle.digest('SHA-1', (new TextEncoder()).encode(txt.toLowerCase()))
));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
},
fromChars = from =>
((from.name || from.email).split(/[^\p{L}]+/gu) || [])
.reduce((a, s) => a + (s[0] || ''), '')
.slice(0,2)
.toUpperCase(),
runQueue = (() => {
let item = queue.shift();
while (item) {
@ -27,7 +42,8 @@
item[1](url);
} else if (window.identiconSvg) {
// rl.pluginSettingsGet('avatars', 'identicon');
window.identiconSvg(from.email).then(url => {
hash(from.email).then(hash => {
let url = window.identiconSvg(hash, fromChars(from));
avatars.set(getAvatarUid(item[0]), url);
item[1](url);
});
@ -81,7 +97,8 @@
for (let i = 0; i < responseList.length; ++i) {
const item = responseList.item(i);
if (1 == getElementValue(item, nsNC, 'has-photo')) {
[...getElementValue(item, nsCard, 'address-data').matchAll(/EMAIL.*?:([^@\r\n]+@[^@\r\n]+)/g)].forEach(match => {
[...getElementValue(item, nsCard, 'address-data').matchAll(/EMAIL.*?:([^@\r\n]+@[^@\r\n]+)/g)]
.forEach(match => {
ncAvatars.set(
match[1].toLowerCase(),
getElementValue(item, nsDAV, 'href') + '?photo'
@ -102,10 +119,13 @@
if (url) {
fn(url);
} else if (msg.avatar) {
let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0;
let from = msg.from[0],
bimi = 'pass' == from.dkimStatus ? 1 : 0;
if (window.identiconSvg) {
// rl.pluginSettingsGet('avatars', 'identicon');
element.onerror = () => window.identiconSvg(msg.from[0].email).then(fn);
element.onerror = () => hash(from.email).then(hash =>
fn(window.identiconSvg(hash, fromChars(from)))
);
}
fn(`?Avatar/${bimi}/${msg.avatar}`);
} else {

View file

@ -0,0 +1,69 @@
(()=>{
const size = 50,
margin = 0.08;
window.identiconSvg = (hash, txt) => {
// color defaults to last 7 chars as hue at 70% saturation, 50% brightness
// hsl2rgb adapted from: https://gist.github.com/aemkei/1325937
let h = (parseInt(hash.substr(-7), 16) / 0xfffffff) * 6, s = 0.7, l = 0.5,
v = [
l += s *= l < .5 ? l : 1 - l,
l - h % 1 * s * 2,
l -= s *= 2,
l,
l + h % 1 * s,
l + s
];
if (txt) {
const color = 'rgb(' + [
v[ ~~h % 6 ] * 255 / 2, // red
v[ (h | 16) % 6 ] * 255 / 2, // green
v[ (h | 8) % 6 ] * 255 / 2 // blue
].map(Math.round).join(',') + ')';
txt = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}px" height="${size}px" viewBox="0 0 ${size} ${size}" version="1.1">
<circle fill="${color}" width="${size}" height="${size}" cx="${size/2}" cy="${size/2}" r="${size/2}"/>
<text x="${size}%" y="${size}%" style="color:#FFF" alignment-baseline="middle" text-anchor="middle"
font-weight="bold" font-size="${Math.round(size*0.5)}"
dy=".1em" dominant-baseline="middle" fill="#FFF">${txt}</text>
</svg>`;
} else {
const color = 'rgb(' + [
v[ ~~h % 6 ] * 255, // red
v[ (h | 16) % 6 ] * 255, // green
v[ (h | 8) % 6 ] * 255 // blue
].map(Math.round).join(',') + ')',
cell = Math.floor((size - ((Math.floor(size * margin)) * 2)) / 5),
imargin = Math.floor((size - cell * 5) / 2),
rectangles = [],
add = (x, y) => rectangles.push("<rect x='" + (x * cell + imargin)
+ "' y='" + (y * cell + imargin)
+ "' width='" + cell + "' height='" + cell + "'/>");
// the first 15 characters of the hash control the pixels (even/odd)
// they are drawn down the middle first, then mirrored outwards
for (let i = 0; i < 15; ++i) {
if (!(parseInt(hash.charAt(i), 16) % 2)) {
if (i < 5) {
add(2, i);
} else if (i < 10) {
add(1, (i - 5));
add(3, (i - 5));
} else if (i < 15) {
add(0, (i - 10));
add(4, (i - 10));
}
}
}
txt = "<g style='fill:" + color + "; stroke:" + color + "; stroke-width:" + (size * 0.005) + ";'>"
+ rectangles.join('')
+ "</g>";
}
return 'data:image/svg+xml;base64,' + btoa(
"<svg xmlns='http://www.w3.org/2000/svg' width='" + size + "' height='" + size + "'>" + txt + "</svg>"
);
};
})();

View file

@ -19,7 +19,9 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addJs('avatars.js');
$this->addJsonHook('Avatar', 'DoAvatar');
$this->addPartHook('Avatar', 'ServiceAvatar');
$this->Config()->Get('plugin', 'identicon', false) && $this->addJs('jdenticon.js');
// $this->Config()->Get('plugin', 'identicon', false) && $this->addJs('jdenticon.js');
// GitHub-style
$this->Config()->Get('plugin', 'identicon', false) && $this->addJs('identicon.js');
// https://github.com/the-djmaze/snappymail/issues/714
$this->Config()->Get('plugin', 'delay', true) || $this->addHook('filter.json-response', 'FilterJsonResponse');
}

View file

@ -386,21 +386,6 @@ function outerShape(index, g, cell) {
);
}
/**
* Computes a SHA1 hash for any value and returns it as a hexadecimal string.
*
* This function is optimized for minimal code size and rather short messages.
*
* @param {string} message
*/
async function computeHash(message) {
const hashArray = Array.from(new Uint8Array(
// await crypto.subtle.digest('SHA-256', (new TextEncoder()).encode(message))
await crypto.subtle.digest('SHA-1', (new TextEncoder()).encode(message))
));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
}
/**
* Prepares a measure to be used as a measure in an SVG path, by
* rounding the measure to a single decimal. This reduces the file
@ -597,11 +582,9 @@ class SvgWriter
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
* @returns {string} SVG string
*/
window.identiconSvg = async (hashOrValue) => {
window.identiconSvg = hash => {
const writer = new SvgWriter(50),
renderer = new SvgRenderer(writer),
hash = (/^[0-9a-f]{11,}$/i.test(hashOrValue) && hashOrValue)
|| await computeHash(hashOrValue == null ? "" : "" + hashOrValue),
config = {
p/*colorSaturation*/: 0.5,
H/*grayscaleSaturation*/: 0,