From 67bb0cdd4ae2f2dc7405d1da71c36e7e705e3e50 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Wed, 11 Nov 2020 15:57:31 -0800 Subject: [PATCH 1/6] Valid JSON? --- app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.json b/app.json index 5bfd980..44b5003 100644 --- a/app.json +++ b/app.json @@ -14,5 +14,5 @@ "telegram", "twitter", "youtube" - ], + ] } From d02a2bd935276f35dfdce511c97fa8c28e953ca2 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 20 Nov 2020 06:25:52 -0800 Subject: [PATCH 2/6] feat: add a `fallback.svg` for dynamic fallbacks --- src/index.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/index.js b/src/index.js index 299189f..2c8f037 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ const { forEach } = require('lodash') const express = require('express') const path = require('path') const { Router } = express +const { get } = require('lodash') const { CACHE_TTL, NODE_ENV, LOG_LEVEL } = require('./constant') const createGetAvatarUrl = require('./avatar-url') @@ -38,6 +39,28 @@ router.use(express.static(path.resolve('public'))) router.get('/robots.txt', (req, res) => res.status(204).send()) router.get('/favicon.ico', (req, res) => res.status(204).send()) +router.get('/fallback.svg', (req, res) => { + const start = get(req, 'query.start', '64748B').replace(/[^a-zA-Z0-9]/, '') + const end = get(req, 'query.end', '64748B').replace(/[^a-zA-Z0-9]/, '') + const width = parseInt(get(req, 'query.width', 256)) + const height = parseInt(get(req, 'query.height', 256)) + const size = parseInt(get(req, 'query.height', 96)) + const text = get(req, 'query.text', '').replace(/[\W_]+/g, '') + res.set('Content-Type', 'image/svg+xml') + const svg = ` + + + + + + + + + ${text} + + ` + res.send(svg) +}) const getAvatar = createGetAvatarUrl() From 270d3eb298d04fd996a763d828f53a643202a56c Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 20 Nov 2020 06:49:35 -0800 Subject: [PATCH 3/6] chore: set the text a little lower --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 2c8f037..d28d687 100644 --- a/src/index.js +++ b/src/index.js @@ -56,7 +56,7 @@ router.get('/fallback.svg', (req, res) => { - ${text} + ${text} ` res.send(svg) From 30c322042d12c8b02789a37e2abd935e0ed13dae Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 4 Dec 2020 05:58:16 -0800 Subject: [PATCH 4/6] refactor: rename `fallback.svg` to `identicon.svg` and abstract to provider --- src/index.js | 23 +++-------------------- src/providers/identicon.js | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 src/providers/identicon.js diff --git a/src/index.js b/src/index.js index d28d687..8c9fe3a 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ const { get } = require('lodash') const { CACHE_TTL, NODE_ENV, LOG_LEVEL } = require('./constant') const createGetAvatarUrl = require('./avatar-url') const { providers } = require('./providers') +const getIdenticon = require('./providers/identicon') const send = require('./send') const ssrCache = (() => { @@ -39,27 +40,9 @@ router.use(express.static(path.resolve('public'))) router.get('/robots.txt', (req, res) => res.status(204).send()) router.get('/favicon.ico', (req, res) => res.status(204).send()) -router.get('/fallback.svg', (req, res) => { - const start = get(req, 'query.start', '64748B').replace(/[^a-zA-Z0-9]/, '') - const end = get(req, 'query.end', '64748B').replace(/[^a-zA-Z0-9]/, '') - const width = parseInt(get(req, 'query.width', 256)) - const height = parseInt(get(req, 'query.height', 256)) - const size = parseInt(get(req, 'query.height', 96)) - const text = get(req, 'query.text', '').replace(/[\W_]+/g, '') +router.get('/identicon.svg', (req, res) => { res.set('Content-Type', 'image/svg+xml') - const svg = ` - - - - - - - - - ${text} - - ` - res.send(svg) + res.send(getIdenticon(req.query)) }) const getAvatar = createGetAvatarUrl() diff --git a/src/providers/identicon.js b/src/providers/identicon.js new file mode 100644 index 0000000..db0d3a2 --- /dev/null +++ b/src/providers/identicon.js @@ -0,0 +1,22 @@ +const { get } = require('lodash') + +module.exports = query => { + const start = get(query, 'start', '64748B').replace(/[^a-zA-Z0-9]/, '') + const end = get(query, 'end', '64748B').replace(/[^a-zA-Z0-9]/, '') + const width = parseInt(get(query, 'width', 256)) + const height = parseInt(get(query, 'height', 256)) + const size = parseInt(get(query, 'size', 96)) + const text = get(query, 'text', '').replace(/[\W_]+/g, '') + return ` + + + + + + + + + ${text} + + ` +} From 904c685cedbcf8032e4de8ca72687d682ed1268a Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 4 Dec 2020 06:09:57 -0800 Subject: [PATCH 5/6] feat: generate a default color gradient based on the text --- package.json | 21 ++++++++-- src/providers/identicon.js | 80 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 896f46b..8c97268 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,22 @@ "url": "https://kikobeats.com" }, "contributors": [ - "Omid Nikrah ", - "Hameed Rahamathullah ", - "Reed Jones ", - "Rodrigo Reis " + { + "name": "Omid Nikrah", + "email": "omidnikrah@gmail.com" + }, + { + "name": "Hameed Rahamathullah", + "email": "hameedraha@gmail.com" + }, + { + "name": "Reed Jones", + "email": "reedjones@reedjones.com" + }, + { + "name": "Rodrigo Reis", + "email": "rodrigoreis22@yahoo.com.br" + } ], "repository": { "type": "git", @@ -42,6 +54,7 @@ "beauty-error": "~1.2.5", "cacheable-response": "~2.1.0", "cheerio": "~1.0.0-rc.3", + "color": "~2.0.1", "compression": "~1.7.4", "cors": "~2.8.5", "debug-logfmt": "~1.0.4", diff --git a/src/providers/identicon.js b/src/providers/identicon.js index db0d3a2..7125b4c 100644 --- a/src/providers/identicon.js +++ b/src/providers/identicon.js @@ -1,12 +1,86 @@ +'use strict' + const { get } = require('lodash') +const crypto = require('crypto') +const Color = require('color') + +function djb2 (str) { + let hash = 5381 + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) + hash + str.charCodeAt(i) + } + return hash +} + +function shouldChangeColor (color) { + const rgb = color.rgb().array() + const val = 765 - (rgb[0] + rgb[1] + rgb[2]) + if (val < 250 || val > 700) { + return true + } + return false +} + +function hashStringToColor (str) { + const hash = djb2(str) + const r = (hash & 0xff0000) >> 16 + const g = (hash & 0x00ff00) >> 8 + const b = hash & 0x0000ff + return ( + '#' + + ('0' + r.toString(16)).substr(-2) + + ('0' + g.toString(16)).substr(-2) + + ('0' + b.toString(16)).substr(-2) + ) +} + +function getMatchingColor (firstColor) { + let color = firstColor + if (color.dark()) { + color = color.saturate(0.3).rotate(90) + } else { + color = color.desaturate(0.3).rotate(90) + } + if (shouldChangeColor(color)) { + color = color.rotate(-200).saturate(0.5) + } + return color +} + +function generateGradient (text) { + const hash = crypto + .createHash('md5') + .update(text) + .digest('hex') + + let firstColor = hashStringToColor(hash) + firstColor = new Color(firstColor).saturate(0.5) + + const lightning = firstColor.hsl().color[2] + if (lightning < 25) { + firstColor = firstColor.lighten(3) + } + if (lightning > 25 && lightning < 40) { + firstColor = firstColor.lighten(0.8) + } + if (lightning > 75) { + firstColor = firstColor.darken(0.4) + } + + return { + defaultStart: firstColor.hex(), + defaultEnd: getMatchingColor(firstColor).hex() + } +} module.exports = query => { - const start = get(query, 'start', '64748B').replace(/[^a-zA-Z0-9]/, '') - const end = get(query, 'end', '64748B').replace(/[^a-zA-Z0-9]/, '') + const text = get(query, 'text', '').replace(/[\W_]+/g, '') + const { defaultStart, defaultEnd } = generateGradient(text) + const start = get(query, 'start', defaultStart).replace(/[^a-zA-Z0-9]/, '') + const end = get(query, 'end', defaultEnd).replace(/[^a-zA-Z0-9]/, '') const width = parseInt(get(query, 'width', 256)) const height = parseInt(get(query, 'height', 256)) const size = parseInt(get(query, 'size', 96)) - const text = get(query, 'text', '').replace(/[\W_]+/g, '') return ` From defe96dcc3ae4125c41015b5d6586aa50180e17a Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 4 Dec 2020 06:13:39 -0800 Subject: [PATCH 6/6] chore: remove unused `get` --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index 8c9fe3a..a206a31 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,6 @@ const { forEach } = require('lodash') const express = require('express') const path = require('path') const { Router } = express -const { get } = require('lodash') const { CACHE_TTL, NODE_ENV, LOG_LEVEL } = require('./constant') const createGetAvatarUrl = require('./avatar-url')