From 18bc4a49e8f9e660672452e0833937d41873531e Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Mon, 15 Apr 2019 20:37:21 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=A9=E3=83=B3=E3=83=80=E3=83=A0=E3=81=AB?=
 =?UTF-8?q?=E3=82=A2=E3=83=90=E3=82=BF=E3=83=BC=E3=82=92=E7=94=9F=E6=88=90?=
 =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 package.json                      |   3 +
 src/misc/gen-avatar.ts            |  89 ++++++++++++++++++++++++++++++
 src/models/repositories/user.ts   |   3 +-
 src/server/file/assets/avatar.jpg | Bin 1261 -> 0 bytes
 src/server/file/index.ts          |   6 --
 src/server/index.ts               |   7 +++
 6 files changed, 101 insertions(+), 7 deletions(-)
 create mode 100644 src/misc/gen-avatar.ts
 delete mode 100644 src/server/file/assets/avatar.jpg

diff --git a/package.json b/package.json
index 5526fdab77..315c9ffcc9 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
 		"@types/portscanner": "2.1.0",
 		"@types/pug": "2.0.4",
 		"@types/qrcode": "1.3.2",
+		"@types/random-seed": "0.3.3",
 		"@types/ratelimiter": "2.1.28",
 		"@types/redis": "2.8.12",
 		"@types/rename": "1.0.1",
@@ -103,6 +104,7 @@
 		"bootstrap-vue": "2.0.0-rc.13",
 		"bull": "3.7.0",
 		"cafy": "15.1.1",
+		"canvas": "2.4.1",
 		"chai": "4.2.0",
 		"chalk": "2.4.2",
 		"cli-highlight": "2.1.0",
@@ -188,6 +190,7 @@
 		"pug": "2.0.3",
 		"punycode": "2.1.1",
 		"qrcode": "1.3.3",
+		"random-seed": "0.3.0",
 		"randomcolor": "0.5.4",
 		"ratelimiter": "3.3.0",
 		"recaptcha-promise": "0.1.3",
diff --git a/src/misc/gen-avatar.ts b/src/misc/gen-avatar.ts
new file mode 100644
index 0000000000..7d22ee98e2
--- /dev/null
+++ b/src/misc/gen-avatar.ts
@@ -0,0 +1,89 @@
+/**
+ * Random avatar generator
+ */
+
+import { createCanvas } from 'canvas';
+import * as gen from 'random-seed';
+
+const size = 512; // px
+const n = 5; // resolution
+const margin = (size / n) / 1.5;
+const colors = [
+	'#e57373',
+	'#F06292',
+	'#BA68C8',
+	'#9575CD',
+	'#7986CB',
+	'#64B5F6',
+	'#4FC3F7',
+	'#4DD0E1',
+	'#4DB6AC',
+	'#81C784',
+	'#8BC34A',
+	'#AFB42B',
+	'#F57F17',
+	'#FF5722',
+	'#795548',
+	'#455A64',
+];
+const bg = '#e9e9e9';
+
+const actualSize = size - (margin * 2);
+const cellSize = actualSize / n;
+const sideN = Math.floor(n / 2);
+
+/**
+ * Generate buffer of random avatar by seed
+ */
+export function genAvatar(seed: string) {
+	const rand = gen.create(seed);
+	const canvas = createCanvas(size, size);
+	const ctx = canvas.getContext('2d');
+
+	ctx.fillStyle = bg;
+	ctx.beginPath();
+	ctx.fillRect(0, 0, size, size);
+
+	ctx.fillStyle = colors[rand(colors.length)];
+
+	// side bitmap (filled by false)
+	const side: boolean[][] = new Array(sideN);
+	for (let i = 0; i < side.length; i++) {
+		side[i] = new Array(n).fill(false);
+	}
+
+	// 1*n (filled by false)
+	const center: boolean[] = new Array(n).fill(false);
+
+	// tslint:disable-next-line:prefer-for-of
+	for (let x = 0; x < side.length; x++) {
+		for (let y = 0; y < side[x].length; y++) {
+			side[x][y] = rand(3) === 0;
+		}
+	}
+
+	for (let i = 0; i < center.length; i++) {
+		center[i] = rand(3) === 0;
+	}
+
+	// Draw
+	for (let x = 0; x < n; x++) {
+		for (let y = 0; y < n; y++) {
+			const isXCenter = x === ((n - 1) / 2);
+			if (isXCenter && !center[y]) continue;
+
+			const isLeftSide = x < ((n - 1) / 2);
+			if (isLeftSide && !side[x][y]) continue;
+
+			const isRightSide = x > ((n - 1) / 2);
+			if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
+
+			const actualX = margin + (cellSize * x);
+			const actualY = margin + (cellSize * y);
+			ctx.beginPath();
+			ctx.fillRect(actualX, actualY, cellSize, cellSize);
+		}
+	}
+
+	return canvas.toBuffer();
+}
diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts
index 83cca2f883..9e4247545a 100644
--- a/src/models/repositories/user.ts
+++ b/src/models/repositories/user.ts
@@ -3,6 +3,7 @@ import { User, ILocalUser, IRemoteUser } from '../entities/user';
 import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
 import rap from '@prezzemolo/rap';
 import { ensure } from '../../prelude/ensure';
+import config from '../../config';
 
 @EntityRepository(User)
 export class UserRepository extends Repository<User> {
@@ -88,7 +89,7 @@ export class UserRepository extends Repository<User> {
 			name: user.name,
 			username: user.username,
 			host: user.host,
-			avatarUrl: user.avatarUrl,
+			avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id,
 			avatarColor: user.avatarColor,
 			isAdmin: user.isAdmin || undefined,
 			isBot: user.isBot || undefined,
diff --git a/src/server/file/assets/avatar.jpg b/src/server/file/assets/avatar.jpg
deleted file mode 100644
index be0c3ca82956fbac1b00b9ebb9e10a87d6ff8d53..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1261
zcmex=<NpH&0WUXCHwH#VMg|WcWcYuZ!I^=DiJ6(1i3Lco00Ao}8!HfSb8xV6@^bU?
z@^bU=@Ck?r^6?Av^Y92s2nmadiHnQ#2}(*!ib;uxiHm^@VFc-9W#wXH;}YZN;TIzr
z{6D}T$idLS(7?>7#K0uT$SlbC{|JLL(5Vc}Am_pXJ2ML#D-#D$(n5fNiHVVkiJgU$
znH3_>$i&RTD#)hDE+lN|7?@btIB}tfsFG38#)A((C>w8bN^Y8T5vWrH*+7U+kf<P%
zHBix83_Q$?K=&{UGT1Zx6@j|~iNQEotb3D@qO0hs(>b*f$F$3CNF2QX;X2dB5Yb<|
z-{xf39TK`F_-*Ui{ptFd*Af#F6OXz}3v~tk5na(J!n)m<Us%gFW=>R||DjV?_8U)o
zWXF2=MDuR1=@-|%f3xKFy7*Op_A<<EHY+pzQ5k&v<hsz?(YFHY*aTn>V6|R+PQGpC
z@-=5V^q0kLuU=Q``rWna=N}tB<;D%k^N;+vayCCk^_Krt_j&ieZ~8v<0!X9fKg$c*
z*Gyjetx%om6TK?VD8BKRJ-_;U)k%Ivg_8<1-&}t0dS+_L^hOyy&8>&5oz=9SOrL$%
z``PU8jSpZJzxt)%`O)_F(p^WU-T&U`sVXazdR(}B<FEG__phzoAGWjRk``Dk=X;;d
z+RT=3&u^Tb|EEh(|7Eal%EypZ-FK6&O5M9x^W;Cn>HGqj8&G5US3TE=lsx2T)a_>!
z`|{`1bAnf2T`TGOWOaI5Q{X=BzsCO=j^Ep#<Wyy2yfXdet|>VOf;JWZHd?B#n)NA@
zF_~@N#EE%vBKo_9?nIt`ADVYJ^>>YJ+XDvPT5qL_9mV@3u08S6o*KI3se<W_$-#>~
zF1<OkbJ?S9>!d%=*(3h>aFOHG(wUZKDY1H6r=2}j^f~w4dH3$pSm%%{OLbm&O^tgc
z@z!K@-0{cH&TBy3%kFwW#_DU&@rjdW9bV+U{@;`RTz~5e>@1vHH+@~H{U)!+<mZj+
zmcLx<_TIbHz38P>-BhRb+>(xOw=BE%Y5Lax41zmD3s!B7EevcC3hatjncidsGMI7U
znwM5}tKCy)_i~%Xr^R0G-xT{|?#Fhnrx|ab1l|2n{hy&Ye{1}b>92o^8q01ybn9-J
ztCz5^mr?Yx`OQpneEm!KWPUByOienv$9n0e*uPWubAS2tFaI3a?2Stk@2<!)c^I&L
z)5{fWe@~xX*O~YCXuHRu#eSDp9Xj53^{U*eI^7@JU)?wEc~S2lyk9+dPI>5m2Djy}
zex821{pj2eP`@)s?OG>m`9tjCeUaC@Y{Q@Jnf29wrrb4ywU@VAe^~$f%h}_#`#ycB
z`?^^-o&7S~oGBZ(dOhAA{mX3Y$L{|O`V(X<faY+Wcb~CZyk(=<ZR@1BlRv+<y?#3R
zw&|8xQ?0^GHFH)k6a3+-oC33t@zKY3`j;*Ijiig$?YpLb^7vc(N!j<R-FTh2-blZy
Rx$<gCo#p~12L}89Hvv>3^BDjD

diff --git a/src/server/file/index.ts b/src/server/file/index.ts
index e3487a2636..1cdf5207e4 100644
--- a/src/server/file/index.ts
+++ b/src/server/file/index.ts
@@ -21,12 +21,6 @@ app.use(async (ctx, next) => {
 // Init router
 const router = new Router();
 
-router.get('/default-avatar.jpg', ctx => {
-	const file = fs.createReadStream(`${__dirname}/assets/avatar.jpg`);
-	ctx.set('Content-Type', 'image/jpeg');
-	ctx.body = file;
-});
-
 router.get('/app-default.jpg', ctx => {
 	const file = fs.createReadStream(`${__dirname}/assets/dummy.png`);
 	ctx.set('Content-Type', 'image/jpeg');
diff --git a/src/server/index.ts b/src/server/index.ts
index 601e288f3b..7d8938d584 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -25,6 +25,7 @@ import Logger from '../services/logger';
 import { program } from '../argv';
 import { UserProfiles } from '../models';
 import { networkChart } from '../services/chart';
+import { genAvatar } from '../misc/gen-avatar';
 
 export const serverLogger = new Logger('server', 'gray', false);
 
@@ -72,6 +73,12 @@ router.use(activityPub.routes());
 router.use(nodeinfo.routes());
 router.use(wellKnown.routes());
 
+router.get('/avatar/:x', ctx => {
+	const avatar = genAvatar(ctx.params.x);
+	ctx.set('Content-Type', 'image/png');
+	ctx.body = avatar;
+});
+
 router.get('/verify-email/:code', async ctx => {
 	const profile = await UserProfiles.findOne({
 		emailVerifyCode: ctx.params.code