From a9436306abbb504e7746ff5821b6fe2daac1e4fe Mon Sep 17 00:00:00 2001
From: Aya Morisawa <AyaMorisawa4869@gmail.com>
Date: Fri, 9 Nov 2018 11:01:55 +0900
Subject: [PATCH 1/3] Fix #3170 (#3173)

---
 src/prelude/array.ts                  |  4 ++++
 src/remote/activitypub/models/note.ts | 14 ++++----------
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/prelude/array.ts b/src/prelude/array.ts
index 54f7081712..42f05dc0c9 100644
--- a/src/prelude/array.ts
+++ b/src/prelude/array.ts
@@ -18,6 +18,10 @@ export function erase<T>(x: T, xs: T[]): T[] {
 	return xs.filter(y => x !== y);
 }
 
+export function setDifference<T>(xs: T[], ys: T[]): T[] {
+	return xs.filter(x => !ys.includes(x));
+}
+
 export function unique<T>(xs: T[]): T[] {
 	return [...new Set(xs)];
 }
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index f9380eb4f4..ce9a4daf16 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -13,7 +13,7 @@ import htmlToMFM from '../../../mfm/html-to-mfm';
 import Emoji from '../../../models/emoji';
 import { ITag } from './tag';
 import { toUnicode } from 'punycode';
-import { unique } from '../../../prelude/array';
+import { unique, concat, setDifference } from '../../../prelude/array';
 
 const log = debug('misskey:activitypub');
 
@@ -179,15 +179,9 @@ async function extractEmojis(tags: ITag[], host_: string) {
 	);
 }
 
-async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver ) {
-	let uris = [] as string[];
-
-	if (to) uris.concat(to);
-	if (cc) uris.concat(cc);
-
-	uris = uris.filter(x => x !== 'https://www.w3.org/ns/activitystreams#Public');
-	uris = uris.filter(x => x !== `${actor.uri}/followers`);
-	uris = unique(uris);
+async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) {
+	const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`];
+	const uris = setDifference(unique(concat([to || [], cc || []])), ignoreUris);
 
 	const users = await Promise.all(
 		uris.map(async uri => await resolvePerson(uri, null, resolver).catch(() => null))

From 276edd7cc2ca38bf4d3e5f47add2ff993e7a7364 Mon Sep 17 00:00:00 2001
From: Aya Morisawa <AyaMorisawa4869@gmail.com>
Date: Fri, 9 Nov 2018 11:02:23 +0900
Subject: [PATCH 2/3] Use sum function (#3174)

---
 src/server/index.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/server/index.ts b/src/server/index.ts
index f1933dc405..77c869bb4e 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -19,6 +19,7 @@ import webFinger from './webfinger';
 import config from '../config';
 import networkChart from '../chart/network';
 import apiServer from './api';
+import { sum } from '../prelude/array';
 
 // Init app
 const app = new Koa();
@@ -99,9 +100,9 @@ export default () => new Promise(resolve => {
 		if (queue.length == 0) return;
 
 		const requests = queue.length;
-		const time = queue.reduce((a, b) => a + b.time, 0);
-		const incomingBytes = queue.reduce((a, b) => a + b.req.bytes, 0);
-		const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
+		const time = sum(queue.map(x => x.time));
+		const incomingBytes = sum(queue.map(x => x.req.byets));
+		const outgoingBytes = sum(queue.map(x => x.res.byets));
 		queue = [];
 
 		networkChart.update(requests, time, incomingBytes, outgoingBytes);

From 500fc47618014b17166ef928767f12c00257b080 Mon Sep 17 00:00:00 2001
From: Aya Morisawa <AyaMorisawa4869@gmail.com>
Date: Fri, 9 Nov 2018 13:03:46 +0900
Subject: [PATCH 3/3] Add group function (#3175)

---
 src/mfm/parse/index.ts | 21 +++++++++------------
 src/prelude/array.ts   | 16 ++++++++++++++++
 src/prelude/string.ts  |  4 ++++
 3 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/src/mfm/parse/index.ts b/src/mfm/parse/index.ts
index 105378343b..f2beec14ff 100644
--- a/src/mfm/parse/index.ts
+++ b/src/mfm/parse/index.ts
@@ -15,6 +15,9 @@ import { TextElementSearch } from './elements/search';
 import { TextElementTitle } from './elements/title';
 import { TextElementUrl } from './elements/url';
 import { TextElementMotion } from './elements/motion';
+import { groupOn } from '../../prelude/array';
+import * as A from '../../prelude/array';
+import * as S from '../../prelude/string';
 
 const elements = [
 	require('./elements/big'),
@@ -89,16 +92,10 @@ export default (source: string): TextElement[] => {
 		i++;
 	}
 
-	// テキストを纏める
-	return tokens.reduce((a, b) => {
-		if (a.length && a[a.length - 1].type == 'text' && b.type == 'text') {
-			const tail = a.pop();
-			return a.concat({
-				type: 'text',
-				content: tail.content + b.content
-			});
-		} else {
-			return a.concat(b);
-		}
-	}, [] as TextElement[]);
+	const combineText = (es: TextElement[]): TextElement =>
+		({ type: 'text', content: S.concat(es.map(e => e.content)) });
+
+	return A.concat(groupOn(x => x.type, tokens).map(es =>
+		es[0].type === 'text' ? [combineText(es)] : es
+	));
 };
diff --git a/src/prelude/array.ts b/src/prelude/array.ts
index 42f05dc0c9..8536e486d6 100644
--- a/src/prelude/array.ts
+++ b/src/prelude/array.ts
@@ -29,3 +29,19 @@ export function unique<T>(xs: T[]): T[] {
 export function sum(xs: number[]): number {
 	return xs.reduce((a, b) => a + b, 0);
 }
+
+export function groupBy<T>(f: (x: T, y: T) => boolean, xs: T[]): T[][] {
+	const groups = [] as T[][];
+	for (const x of xs) {
+		if (groups.length !== 0 && f(groups[groups.length - 1][0], x)) {
+			groups[groups.length - 1].push(x);
+		} else {
+			groups.push([x]);
+		}
+	}
+	return groups;
+}
+
+export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
+	return groupBy((a, b) => f(a) === f(b), xs);
+}
diff --git a/src/prelude/string.ts b/src/prelude/string.ts
index cae776bc3d..6149235e47 100644
--- a/src/prelude/string.ts
+++ b/src/prelude/string.ts
@@ -1,3 +1,7 @@
+export function concat(xs: string[]): string {
+	return xs.reduce((a, b) => a + b, "");
+}
+
 export function capitalize(s: string): string {
 	return toUpperCase(s.charAt(0)) + toLowerCase(s.slice(1));
 }