diff --git a/package.json b/package.json
index b721682d41..5018fadf60 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
 		"@types/license-checker": "15.0.0",
 		"@types/mkdirp": "0.5.2",
 		"@types/mocha": "5.0.0",
-		"@types/mongodb": "3.0.9",
+		"@types/mongodb": "3.0.10",
 		"@types/monk": "6.0.0",
 		"@types/morgan": "1.7.35",
 		"@types/ms": "0.7.30",
@@ -203,13 +203,13 @@
 		"vue-cropperjs": "2.2.0",
 		"vue-js-modal": "1.3.12",
 		"vue-json-tree-view": "2.1.3",
-		"vue-loader": "14.2.2",
+		"vue-loader": "15.0.0-rc.1",
 		"vue-router": "3.0.1",
 		"vue-template-compiler": "2.5.16",
 		"vuedraggable": "2.16.0",
 		"web-push": "3.3.0",
 		"webfinger.js": "2.6.6",
-		"webpack": "4.4.1",
+		"webpack": "4.5.0",
 		"webpack-cli": "2.0.14",
 		"webpack-replace-loader": "1.3.0",
 		"websocket": "1.0.25",
diff --git a/src/client/app/ch/tags/channel.tag b/src/client/app/ch/tags/channel.tag
index 1ebc3ccebc..4856728dec 100644
--- a/src/client/app/ch/tags/channel.tag
+++ b/src/client/app/ch/tags/channel.tag
@@ -165,7 +165,7 @@
 <mk-channel-post>
 	<header>
 		<a class="index" @click="reply">{ post.index }:</a>
-		<a class="name" href={ _URL_ + '/@' + acct }><b>{ post.user.name }</b></a>
+		<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(post.user) }</b></a>
 		<mk-time time={ post.createdAt }/>
 		<mk-time time={ post.createdAt } mode="detail"/>
 		<span>ID:<i>{ acct }</i></span>
@@ -230,10 +230,12 @@
 	</style>
 	<script lang="typescript">
 		import getAcct from '../../../../acct/render';
+		import getUserName from '../../../../renderers/get-user-name';
 
 		this.post = this.opts.post;
 		this.form = this.opts.form;
 		this.acct = getAcct(this.post.user);
+		this.name = getUserName(this.post.user);
 
 		this.reply = () => {
 			this.form.update({
@@ -244,7 +246,7 @@
 </mk-channel-post>
 
 <mk-channel-form>
-	<p v-if="reply"><b>&gt;&gt;{ reply.index }</b> ({ reply.user.name }): <a @click="clearReply">[x]</a></p>
+	<p v-if="reply"><b>&gt;&gt;{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p>
 	<textarea ref="text" disabled={ wait } oninput={ update } onkeydown={ onkeydown } onpaste={ onpaste } placeholder="%i18n:ch.tags.mk-channel-form.textarea%"></textarea>
 	<div class="actions">
 		<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button>
@@ -286,6 +288,8 @@
 
 	</style>
 	<script lang="typescript">
+		import getUserName from '../../../../renderers/get-user-name';
+
 		this.mixin('api');
 
 		this.channel = this.opts.channel;
@@ -373,6 +377,8 @@
 				}
 			});
 		};
+
+		this.getUserName = getUserName;
 	</script>
 </mk-channel-form>
 
diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts
index ebc15952f6..e99d502960 100644
--- a/src/client/app/common/scripts/compose-notification.ts
+++ b/src/client/app/common/scripts/compose-notification.ts
@@ -1,5 +1,6 @@
 import getPostSummary from '../../../../renderers/get-post-summary';
 import getReactionEmoji from '../../../../renderers/get-reaction-emoji';
+import getUserName from '../../../../renderers/get-user-name';
 
 type Notification = {
 	title: string;
@@ -21,35 +22,35 @@ export default function(type, data): Notification {
 
 		case 'mention':
 			return {
-				title: `${data.user.name}さんから:`,
+				title: `${getUserName(data.user)}さんから:`,
 				body: getPostSummary(data),
 				icon: data.user.avatarUrl + '?thumbnail&size=64'
 			};
 
 		case 'reply':
 			return {
-				title: `${data.user.name}さんから返信:`,
+				title: `${getUserName(data.user)}さんから返信:`,
 				body: getPostSummary(data),
 				icon: data.user.avatarUrl + '?thumbnail&size=64'
 			};
 
 		case 'quote':
 			return {
-				title: `${data.user.name}さんが引用:`,
+				title: `${getUserName(data.user)}さんが引用:`,
 				body: getPostSummary(data),
 				icon: data.user.avatarUrl + '?thumbnail&size=64'
 			};
 
 		case 'reaction':
 			return {
-				title: `${data.user.name}: ${getReactionEmoji(data.reaction)}:`,
+				title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
 				body: getPostSummary(data.post),
 				icon: data.user.avatarUrl + '?thumbnail&size=64'
 			};
 
 		case 'unread_messaging_message':
 			return {
-				title: `${data.user.name}さんからメッセージ:`,
+				title: `${getUserName(data.user)}さんからメッセージ:`,
 				body: data.text, // TODO: getMessagingMessageSummary(data),
 				icon: data.user.avatarUrl + '?thumbnail&size=64'
 			};
@@ -57,7 +58,7 @@ export default function(type, data): Notification {
 		case 'othello_invited':
 			return {
 				title: '対局への招待があります',
-				body: `${data.parent.name}さんから`,
+				body: `${getUserName(data.parent)}さんから`,
 				icon: data.parent.avatarUrl + '?thumbnail&size=64'
 			};
 
diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue
index 38eaf86508..8837fde6be 100644
--- a/src/client/app/common/views/components/autocomplete.vue
+++ b/src/client/app/common/views/components/autocomplete.vue
@@ -3,7 +3,7 @@
 	<ol class="users" ref="suggests" v-if="users.length > 0">
 		<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
 			<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
-			<span class="name">{{ user.name }}</span>
+			<span class="name">{{ getUserName(user) }}</span>
 			<span class="username">@{{ getAcct(user) }}</span>
 		</li>
 	</ol>
@@ -22,6 +22,7 @@ import Vue from 'vue';
 import * as emojilib from 'emojilib';
 import contains from '../../../common/scripts/contains';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 const lib = Object.entries(emojilib.lib).filter((x: any) => {
 	return x[1].category != 'flags';
@@ -107,6 +108,7 @@ export default Vue.extend({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		exec() {
 			this.select = -1;
 			if (this.$refs.suggests) {
diff --git a/src/client/app/common/views/components/messaging.vue b/src/client/app/common/views/components/messaging.vue
index 4ab3e46e89..9b1449daa5 100644
--- a/src/client/app/common/views/components/messaging.vue
+++ b/src/client/app/common/views/components/messaging.vue
@@ -14,7 +14,7 @@
 					tabindex="-1"
 				>
 					<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
-					<span class="name">{{ user.name }}</span>
+					<span class="name">{{ getUserName(user) }}</span>
 					<span class="username">@{{ getAcct(user) }}</span>
 				</li>
 			</ol>
@@ -33,7 +33,7 @@
 				<div>
 					<img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/>
 					<header>
-						<span class="name">{{ isMe(message) ? message.recipient.name : message.user.name }}</span>
+						<span class="name">{{ getUserName(isMe(message) ? message.recipient : message.user) }}</span>
 						<span class="username">@{{ getAcct(isMe(message) ? message.recipient : message.user) }}</span>
 						<mk-time :time="message.createdAt"/>
 					</header>
@@ -52,6 +52,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: {
@@ -94,6 +95,7 @@ export default Vue.extend({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		isMe(message) {
 			return message.userId == (this as any).os.i.id;
 		},
diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue
index ef0c7beaad..bfb4b2bbe0 100644
--- a/src/client/app/common/views/components/welcome-timeline.vue
+++ b/src/client/app/common/views/components/welcome-timeline.vue
@@ -6,7 +6,7 @@
 		</router-link>
 		<div class="body">
 			<header>
-				<router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ post.user.name }}</router-link>
+				<router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ getUserName(post.user) }}</router-link>
 				<span class="username">@{{ getAcct(post.user) }}</span>
 				<div class="info">
 					<router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`">
@@ -25,6 +25,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -38,6 +39,7 @@ export default Vue.extend({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		fetch(cb?) {
 			this.fetching = true;
 			(this as any).api('posts', {
diff --git a/src/client/app/desktop/views/components/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue
index 623971fa33..d37ca745af 100644
--- a/src/client/app/desktop/views/components/followers-window.vue
+++ b/src/client/app/desktop/views/components/followers-window.vue
@@ -1,7 +1,7 @@
 <template>
 <mk-window width="400px" height="550px" @closed="$destroy">
 	<span slot="header" :class="$style.header">
-		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user.name }}のフォロワー
+		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロワー
 	</span>
 	<mk-followers :user="user"/>
 </mk-window>
@@ -9,8 +9,15 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import getUserName from '../../../../../renderers/get-user-name';
+
 export default Vue.extend({
-	props: ['user']
+	props: ['user'],
+	computed {
+		name() {
+			return getUserName(this.user);
+		}
+	}
 });
 </script>
 
diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue
index 612847b386..cbd8ec5f94 100644
--- a/src/client/app/desktop/views/components/following-window.vue
+++ b/src/client/app/desktop/views/components/following-window.vue
@@ -1,7 +1,7 @@
 <template>
 <mk-window width="400px" height="550px" @closed="$destroy">
 	<span slot="header" :class="$style.header">
-		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user.name }}のフォロー
+		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロー
 	</span>
 	<mk-following :user="user"/>
 </mk-window>
@@ -9,8 +9,15 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import getUserName from '../../../../../renderers/get-user-name';
+
 export default Vue.extend({
-	props: ['user']
+	props: ['user'],
+	computed: {
+		name() {
+			return getUserName(this.user);
+		}
+	}
 });
 </script>
 
diff --git a/src/client/app/desktop/views/components/friends-maker.vue b/src/client/app/desktop/views/components/friends-maker.vue
index 351e9e1c5c..acc4542d95 100644
--- a/src/client/app/desktop/views/components/friends-maker.vue
+++ b/src/client/app/desktop/views/components/friends-maker.vue
@@ -7,7 +7,7 @@
 				<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="user.id"/>
 			</router-link>
 			<div class="body">
-				<router-link class="name" :to="`/@${getAcct(user)}`" v-user-preview="user.id">{{ user.name }}</router-link>
+				<router-link class="name" :to="`/@${getAcct(user)}`" v-user-preview="user.id">{{ getUserName(user) }}</router-link>
 				<p class="username">@{{ getAcct(user) }}</p>
 			</div>
 			<mk-follow-button :user="user"/>
@@ -23,6 +23,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -38,6 +39,7 @@ export default Vue.extend({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		fetch() {
 			this.fetching = true;
 			this.users = [];
diff --git a/src/client/app/desktop/views/components/messaging-room-window.vue b/src/client/app/desktop/views/components/messaging-room-window.vue
index f29f9b74e7..7f8c35c2f8 100644
--- a/src/client/app/desktop/views/components/messaging-room-window.vue
+++ b/src/client/app/desktop/views/components/messaging-room-window.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
-	<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ user.name }}</span>
+	<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ name }}</span>
 	<mk-messaging-room :user="user" :class="$style.content"/>
 </mk-window>
 </template>
@@ -9,10 +9,14 @@
 import Vue from 'vue';
 import { url } from '../../../config';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
 	computed: {
+		name(): string {
+			return getUserName(this.user);
+		},
 		popout(): string {
 			return `${url}/i/messaging/${getAcct(this.user)}`;
 		}
diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue
index c5ab284df9..d8b8eab212 100644
--- a/src/client/app/desktop/views/components/notifications.vue
+++ b/src/client/app/desktop/views/components/notifications.vue
@@ -11,7 +11,7 @@
 					<div class="text">
 						<p>
 							<mk-reaction-icon :reaction="notification.reaction"/>
-							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link>
 						</p>
 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">
 							%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%
@@ -24,7 +24,7 @@
 					</router-link>
 					<div class="text">
 						<p>%fa:retweet%
-							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link>
 						</p>
 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">
 							%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%
@@ -37,7 +37,7 @@
 					</router-link>
 					<div class="text">
 						<p>%fa:quote-left%
-							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link>
 						</p>
 						<router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link>
 					</div>
@@ -48,7 +48,7 @@
 					</router-link>
 					<div class="text">
 						<p>%fa:user-plus%
-							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link>
 						</p>
 					</div>
 				</template>
@@ -58,7 +58,7 @@
 					</router-link>
 					<div class="text">
 						<p>%fa:reply%
-							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link>
 						</p>
 						<router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link>
 					</div>
@@ -69,7 +69,7 @@
 					</router-link>
 					<div class="text">
 						<p>%fa:at%
-							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link>
+							<router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link>
 						</p>
 						<a class="post-preview" :href="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</a>
 					</div>
@@ -79,7 +79,7 @@
 						<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
 					</router-link>
 					<div class="text">
-						<p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</a></p>
+						<p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p>
 						<router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">
 							%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%
 						</router-link>
@@ -104,6 +104,7 @@
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
 import getPostSummary from '../../../../../renderers/get-post-summary';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -154,6 +155,7 @@ export default Vue.extend({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		fetchMoreNotifications() {
 			this.fetchingMoreNotifications = true;
 
diff --git a/src/client/app/desktop/views/components/post-detail.sub.vue b/src/client/app/desktop/views/components/post-detail.sub.vue
index 59bc9ce0cf..496003eb8b 100644
--- a/src/client/app/desktop/views/components/post-detail.sub.vue
+++ b/src/client/app/desktop/views/components/post-detail.sub.vue
@@ -6,7 +6,7 @@
 	<div class="main">
 		<header>
 			<div class="left">
-				<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link>
+				<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</router-link>
 				<span class="username">@{{ acct }}</span>
 			</div>
 			<div class="right">
@@ -29,6 +29,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
@@ -36,6 +37,9 @@ export default Vue.extend({
 		acct() {
 			return getAcct(this.post.user);
 		},
+		name() {
+			return getUserName(this.post.user);
+		},
 		title(): string {
 			return dateStringify(this.post.createdAt);
 		}
diff --git a/src/client/app/desktop/views/components/post-detail.vue b/src/client/app/desktop/views/components/post-detail.vue
index 8000ce2e6f..1a3c0d1b68 100644
--- a/src/client/app/desktop/views/components/post-detail.vue
+++ b/src/client/app/desktop/views/components/post-detail.vue
@@ -22,7 +22,7 @@
 				<img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
 			</router-link>
 			%fa:retweet%
-			<router-link class="name" :href="`/@${acct}`">{{ post.user.name }}</router-link>
+			<router-link class="name" :href="`/@${acct}`">{{ getUserName(post.user) }}</router-link>
 			がRepost
 		</p>
 	</div>
@@ -31,7 +31,7 @@
 			<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/>
 		</router-link>
 		<header>
-			<router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link>
+			<router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ getUserName(p.user) }}</router-link>
 			<span class="username">@{{ pAcct }}</span>
 			<router-link class="time" :to="`/@${pAcct}/${p.id}`">
 				<mk-time :time="p.createdAt"/>
@@ -79,6 +79,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 import parse from '../../../../../text/parse';
 
 import MkPostFormWindow from './post-form-window.vue';
@@ -133,9 +134,15 @@ export default Vue.extend({
 		acct(): string {
 			return getAcct(this.post.user);
 		},
+		name(): string {
+			return getUserName(this.post.user);
+		},
 		pAcct(): string {
 			return getAcct(this.p.user);
 		},
+		pName(): string {
+			return getUserName(this.p.user);
+		},
 		urls(): string[] {
 			if (this.p.text) {
 				const ast = parse(this.p.text);
diff --git a/src/client/app/desktop/views/components/post-preview.vue b/src/client/app/desktop/views/components/post-preview.vue
index 7129f67b39..99d9442d93 100644
--- a/src/client/app/desktop/views/components/post-preview.vue
+++ b/src/client/app/desktop/views/components/post-preview.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link>
 			<span class="username">@{{ acct }}</span>
 			<router-link class="time" :to="`/@${acct}/${post.id}`">
 				<mk-time :time="post.createdAt"/>
@@ -22,6 +22,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
@@ -29,6 +30,9 @@ export default Vue.extend({
 		acct() {
 			return getAcct(this.post.user);
 		},
+		name() {
+			return getUserName(this.post.user);
+		},
 		title(): string {
 			return dateStringify(this.post.createdAt);
 		}
diff --git a/src/client/app/desktop/views/components/posts.post.sub.vue b/src/client/app/desktop/views/components/posts.post.sub.vue
index dffecb89cc..a9cd0a9279 100644
--- a/src/client/app/desktop/views/components/posts.post.sub.vue
+++ b/src/client/app/desktop/views/components/posts.post.sub.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link>
 			<span class="username">@{{ acct }}</span>
 			<router-link class="created-at" :to="`/@${acct}/${post.id}`">
 				<mk-time :time="post.createdAt"/>
@@ -22,6 +22,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
@@ -29,6 +30,9 @@ export default Vue.extend({
 		acct() {
 			return getAcct(this.post.user);
 		},
+		name(): string {
+			return getUserName(this.post.user);
+		},
 		title(): string {
 			return dateStringify(this.post.createdAt);
 		}
diff --git a/src/client/app/desktop/views/components/posts.post.vue b/src/client/app/desktop/views/components/posts.post.vue
index 9a13dd6872..17fe330420 100644
--- a/src/client/app/desktop/views/components/posts.post.vue
+++ b/src/client/app/desktop/views/components/posts.post.vue
@@ -10,7 +10,7 @@
 			</router-link>
 			%fa:retweet%
 			<span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span>
-			<a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</a>
+			<a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</a>
 			<span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span>
 		</p>
 		<mk-time :time="post.createdAt"/>
@@ -86,6 +86,7 @@
 import Vue from 'vue';
 import dateStringify from '../../../common/scripts/date-stringify';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 import parse from '../../../../../text/parse';
 
 import MkPostFormWindow from './post-form-window.vue';
@@ -124,6 +125,9 @@ export default Vue.extend({
 		acct(): string {
 			return getAcct(this.p.user);
 		},
+		name(): string {
+			return getUserName(this.p.user);
+		},
 		isRepost(): boolean {
 			return (this.post.repost &&
 				this.post.text == null &&
diff --git a/src/client/app/desktop/views/components/settings.mute.vue b/src/client/app/desktop/views/components/settings.mute.vue
index c87f973faf..6bdc766538 100644
--- a/src/client/app/desktop/views/components/settings.mute.vue
+++ b/src/client/app/desktop/views/components/settings.mute.vue
@@ -5,7 +5,7 @@
 	</div>
 	<div class="users" v-if="users.length != 0">
 		<div v-for="user in users" :key="user.id">
-			<p><b>{{ user.name }}</b> @{{ getAcct(user) }}</p>
+			<p><b>{{ getUserName(user) }}</b> @{{ getAcct(user) }}</p>
 		</div>
 	</div>
 </div>
@@ -14,6 +14,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -23,7 +24,8 @@ export default Vue.extend({
 		};
 	},
 	methods: {
-		getAcct
+		getAcct,
+		getUserName
 	},
 	mounted() {
 		(this as any).api('mute/list').then(x => {
diff --git a/src/client/app/desktop/views/components/settings.profile.vue b/src/client/app/desktop/views/components/settings.profile.vue
index ba86286f87..28be48e0a8 100644
--- a/src/client/app/desktop/views/components/settings.profile.vue
+++ b/src/client/app/desktop/views/components/settings.profile.vue
@@ -42,7 +42,7 @@ export default Vue.extend({
 		};
 	},
 	created() {
-		this.name = (this as any).os.i.name;
+		this.name = (this as any).os.i.name || '';
 		this.location = (this as any).os.i.account.profile.location;
 		this.description = (this as any).os.i.description;
 		this.birthday = (this as any).os.i.account.profile.birthday;
@@ -53,7 +53,7 @@ export default Vue.extend({
 		},
 		save() {
 			(this as any).api('i/update', {
-				name: this.name,
+				name: this.name || null,
 				location: this.location || null,
 				description: this.description || null,
 				birthday: this.birthday || null
diff --git a/src/client/app/desktop/views/components/ui.header.vue b/src/client/app/desktop/views/components/ui.header.vue
index 448d04d261..7d93847fab 100644
--- a/src/client/app/desktop/views/components/ui.header.vue
+++ b/src/client/app/desktop/views/components/ui.header.vue
@@ -4,7 +4,7 @@
 	<div class="main" ref="main">
 		<div class="backdrop"></div>
 		<div class="main">
-			<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p>
+			<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p>
 			<div class="container" ref="mainContainer">
 				<div class="left">
 					<x-nav/>
@@ -33,7 +33,14 @@ import XNotifications from './ui.header.notifications.vue';
 import XPost from './ui.header.post.vue';
 import XClock from './ui.header.clock.vue';
 
+import getUserName from '../../../../../renderers/get-user-name';
+
 export default Vue.extend({
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	},
 	components: {
 		XNav,
 		XSearch,
diff --git a/src/client/app/desktop/views/components/users-list.item.vue b/src/client/app/desktop/views/components/users-list.item.vue
index 2d7d4dc72d..c7a132ecfb 100644
--- a/src/client/app/desktop/views/components/users-list.item.vue
+++ b/src/client/app/desktop/views/components/users-list.item.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`" v-user-preview="user.id">{{ user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`" v-user-preview="user.id">{{ name }}</router-link>
 			<span class="username">@{{ acct }}</span>
 		</header>
 		<div class="body">
@@ -20,12 +20,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
 	computed: {
 		acct() {
 			return getAcct(this.user);
+		},
+		name() {
+			return getUserName(this.user);
 		}
 	}
 });
diff --git a/src/client/app/desktop/views/pages/messaging-room.vue b/src/client/app/desktop/views/pages/messaging-room.vue
index 1e61f3ce17..1cc8d8a778 100644
--- a/src/client/app/desktop/views/pages/messaging-room.vue
+++ b/src/client/app/desktop/views/pages/messaging-room.vue
@@ -8,6 +8,7 @@
 import Vue from 'vue';
 import Progress from '../../../common/scripts/loading';
 import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -34,7 +35,7 @@ export default Vue.extend({
 				this.user = user;
 				this.fetching = false;
 
-				document.title = 'メッセージ: ' + this.user.name;
+				document.title = 'メッセージ: ' + getUserName(this.user);
 
 				Progress.done();
 			});
diff --git a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue
index 7497acd0e0..16625b6899 100644
--- a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue
+++ b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue
@@ -4,7 +4,7 @@
 	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.followers-you-know.loading%<mk-ellipsis/></p>
 	<div v-if="!fetching && users.length > 0">
 	<router-link v-for="user in users" :to="`/@${getAcct(user)}`" :key="user.id">
-		<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user.name" v-user-preview="user.id"/>
+		<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)" v-user-preview="user.id"/>
 	</router-link>
 	</div>
 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:desktop.tags.mk-user.followers-you-know.no-users%</p>
@@ -14,6 +14,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../../acct/render';
+import getUserName from '../../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
@@ -24,7 +25,8 @@ export default Vue.extend({
 		};
 	},
 	method() {
-		getAcct
+		getAcct,
+		getUserName
 	},
 	mounted() {
 		(this as any).api('users/followers', {
diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue
index d30f423d53..5c6746d5dc 100644
--- a/src/client/app/desktop/views/pages/user/user.header.vue
+++ b/src/client/app/desktop/views/pages/user/user.header.vue
@@ -7,7 +7,7 @@
 	<div class="container">
 		<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=150`" alt="avatar"/>
 		<div class="title">
-			<p class="name">{{ user.name }}</p>
+			<p class="name">{{ name }}</p>
 			<p class="username">@{{ acct }}</p>
 			<p class="location" v-if="user.host === null && user.account.profile.location">%fa:map-marker%{{ user.account.profile.location }}</p>
 		</div>
@@ -23,12 +23,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../../acct/render';
+import getUserName from '../../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
 	computed: {
 		acct() {
 			return getAcct(this.user);
+		},
+		name() {
+			return getUserName(this.user);
 		}
 	},
 	mounted() {
diff --git a/src/client/app/desktop/views/pages/user/user.vue b/src/client/app/desktop/views/pages/user/user.vue
index 02ddc2421d..d07b462b59 100644
--- a/src/client/app/desktop/views/pages/user/user.vue
+++ b/src/client/app/desktop/views/pages/user/user.vue
@@ -10,6 +10,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import parseAcct from '../../../../../../acct/parse';
+import getUserName from '../../../../../../renderers/get-user-name';
 import Progress from '../../../../common/scripts/loading';
 import XHeader from './user.header.vue';
 import XHome from './user.home.vue';
@@ -44,7 +45,7 @@ export default Vue.extend({
 				this.user = user;
 				this.fetching = false;
 				Progress.done();
-				document.title = user.name + ' | Misskey';
+				document.title = getUserName(user) + ' | Misskey';
 			});
 		}
 	}
diff --git a/src/client/app/desktop/views/widgets/channel.channel.post.vue b/src/client/app/desktop/views/widgets/channel.channel.post.vue
index e10e9c4f76..fa6d8c34a5 100644
--- a/src/client/app/desktop/views/widgets/channel.channel.post.vue
+++ b/src/client/app/desktop/views/widgets/channel.channel.post.vue
@@ -2,7 +2,7 @@
 <div class="post">
 	<header>
 		<a class="index" @click="reply">{{ post.index }}:</a>
-		<router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ post.user.name }}</b></router-link>
+		<router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ name }}</b></router-link>
 		<span>ID:<i>{{ acct }}</i></span>
 	</header>
 	<div>
@@ -20,12 +20,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
 	computed: {
 		acct() {
 			return getAcct(this.post.user);
+		},
+		name() {
+			return getUserName(this.post.user);
 		}
 	},
 	methods: {
diff --git a/src/client/app/desktop/views/widgets/profile.vue b/src/client/app/desktop/views/widgets/profile.vue
index 83cd67b50c..98e42222ec 100644
--- a/src/client/app/desktop/views/widgets/profile.vue
+++ b/src/client/app/desktop/views/widgets/profile.vue
@@ -15,19 +15,26 @@
 		title="クリックでアバター編集"
 		v-user-preview="os.i.id"
 	/>
-	<router-link class="name" :to="`/@${os.i.username}`">{{ os.i.name }}</router-link>
+	<router-link class="name" :to="`/@${os.i.username}`">{{ name }}</router-link>
 	<p class="username">@{{ os.i.username }}</p>
 </div>
 </template>
 
 <script lang="ts">
 import define from '../../../common/define-widget';
+import getUserName from '../../../../../renderers/get-user-name';
+
 export default define({
 	name: 'profile',
 	props: () => ({
 		design: 0
 	})
 }).extend({
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	},
 	methods: {
 		func() {
 			if (this.props.design == 2) {
diff --git a/src/client/app/desktop/views/widgets/users.vue b/src/client/app/desktop/views/widgets/users.vue
index 6f6a101577..a5dabb68fc 100644
--- a/src/client/app/desktop/views/widgets/users.vue
+++ b/src/client/app/desktop/views/widgets/users.vue
@@ -11,7 +11,7 @@
 				<img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/>
 			</router-link>
 			<div class="body">
-				<router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ _user.name }}</router-link>
+				<router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ getUserName(_user) }}</router-link>
 				<p class="username">@{{ getAcct(_user) }}</p>
 			</div>
 			<mk-follow-button :user="_user"/>
@@ -24,6 +24,7 @@
 <script lang="ts">
 import define from '../../../common/define-widget';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 const limit = 3;
 
@@ -45,6 +46,7 @@ export default define({
 	},
 	methods: {
 		getAcct,
+		getUserName,
 		func() {
 			this.props.compact = !this.props.compact;
 		},
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index 3e5c38961f..2fb8f15cf3 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -14,7 +14,7 @@ import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
 import App from './app.vue';
 import checkForUpdate from './common/scripts/check-for-update';
 import MiOS, { API } from './common/mios';
-import { version, codename, hostname, lang } from './config';
+import { version, codename, lang } from './config';
 
 let elementLocale;
 switch (lang) {
@@ -60,10 +60,6 @@ console.info(
 window.clearTimeout((window as any).mkBootTimer);
 delete (window as any).mkBootTimer;
 
-if (hostname != 'localhost') {
-	document.domain = hostname;
-}
-
 //#region Set lang attr
 const html = document.documentElement;
 html.setAttribute('lang', lang);
diff --git a/src/client/app/mobile/views/components/notification-preview.vue b/src/client/app/mobile/views/components/notification-preview.vue
index 77abd3c0ea..0492c5d86c 100644
--- a/src/client/app/mobile/views/components/notification-preview.vue
+++ b/src/client/app/mobile/views/components/notification-preview.vue
@@ -3,7 +3,7 @@
 	<template v-if="notification.type == 'reaction'">
 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ notification.user.name }}</p>
+			<p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p>
 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
 		</div>
 	</template>
@@ -11,7 +11,7 @@
 	<template v-if="notification.type == 'repost'">
 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:retweet%{{ notification.post.user.name }}</p>
+			<p>%fa:retweet%{{ posterName }}</p>
 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p>
 		</div>
 	</template>
@@ -19,7 +19,7 @@
 	<template v-if="notification.type == 'quote'">
 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:quote-left%{{ notification.post.user.name }}</p>
+			<p>%fa:quote-left%{{ posterName }}</p>
 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 		</div>
 	</template>
@@ -27,14 +27,14 @@
 	<template v-if="notification.type == 'follow'">
 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:user-plus%{{ notification.user.name }}</p>
+			<p>%fa:user-plus%{{ name }}</p>
 		</div>
 	</template>
 
 	<template v-if="notification.type == 'reply'">
 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:reply%{{ notification.post.user.name }}</p>
+			<p>%fa:reply%{{ posterName }}</p>
 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 		</div>
 	</template>
@@ -42,7 +42,7 @@
 	<template v-if="notification.type == 'mention'">
 		<img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:at%{{ notification.post.user.name }}</p>
+			<p>%fa:at%{{ posterName }}</p>
 			<p class="post-preview">{{ getPostSummary(notification.post) }}</p>
 		</div>
 	</template>
@@ -50,7 +50,7 @@
 	<template v-if="notification.type == 'poll_vote'">
 		<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 		<div class="text">
-			<p>%fa:chart-pie%{{ notification.user.name }}</p>
+			<p>%fa:chart-pie%{{ name }}</p>
 			<p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p>
 		</div>
 	</template>
@@ -60,9 +60,18 @@
 <script lang="ts">
 import Vue from 'vue';
 import getPostSummary from '../../../../../renderers/get-post-summary';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['notification'],
+	computed: {
+		name() {
+			return getUserName(this.notification.user);
+		},
+		posterName() {
+			return getUserName(this.notification.post.user);
+		}
+	},
 	data() {
 		return {
 			getPostSummary
diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue
index 189d7195fb..62a0e6425e 100644
--- a/src/client/app/mobile/views/components/notification.vue
+++ b/src/client/app/mobile/views/components/notification.vue
@@ -8,7 +8,7 @@
 		<div class="text">
 			<p>
 				<mk-reaction-icon :reaction="notification.reaction"/>
-				<router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
+				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
 			</p>
 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 				%fa:quote-left%{{ getPostSummary(notification.post) }}
@@ -25,7 +25,7 @@
 		<div class="text">
 			<p>
 				%fa:retweet%
-				<router-link :to="`/@${acct}`">{{ notification.post.user.name }}</router-link>
+				<router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link>
 			</p>
 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 				%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%
@@ -45,7 +45,7 @@
 		<div class="text">
 			<p>
 				%fa:user-plus%
-				<router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
+				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
 			</p>
 		</div>
 	</div>
@@ -66,7 +66,7 @@
 		<div class="text">
 			<p>
 				%fa:chart-pie%
-				<router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link>
+				<router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link>
 			</p>
 			<router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`">
 				%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%
@@ -80,12 +80,19 @@
 import Vue from 'vue';
 import getPostSummary from '../../../../../renderers/get-post-summary';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['notification'],
 	computed: {
 		acct() {
 			return getAcct(this.notification.user);
+		},
+		name() {
+			return getUserName(this.notification.user);
+		},
+		posterName() {
+ 			return getUserName(this.notification.post.user);
 		}
 	},
 	data() {
diff --git a/src/client/app/mobile/views/components/post-card.vue b/src/client/app/mobile/views/components/post-card.vue
index 38d4522d26..68a42ef249 100644
--- a/src/client/app/mobile/views/components/post-card.vue
+++ b/src/client/app/mobile/views/components/post-card.vue
@@ -2,7 +2,7 @@
 <div class="mk-post-card">
 	<a :href="`/@${acct}/${post.id}`">
 		<header>
-			<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ post.user.name }}</h3>
+			<img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3>
 		</header>
 		<div>
 			{{ text }}
@@ -16,6 +16,7 @@
 import Vue from 'vue';
 import summary from '../../../../../renderers/get-post-summary';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
@@ -23,6 +24,9 @@ export default Vue.extend({
 		acct() {
 			return getAcct(this.post.user);
 		},
+		name() {
+			return getUserName(this.post.user);
+		},
 		text(): string {
 			return summary(this.post);
 		}
diff --git a/src/client/app/mobile/views/components/post-detail.sub.vue b/src/client/app/mobile/views/components/post-detail.sub.vue
index 7ff9f1aabd..98d6a14cac 100644
--- a/src/client/app/mobile/views/components/post-detail.sub.vue
+++ b/src/client/app/mobile/views/components/post-detail.sub.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link>
 			<span class="username">@{{ acct }}</span>
 			<router-link class="time" :to="`/@${acct}/${post.id}`">
 				<mk-time :time="post.createdAt"/>
@@ -21,12 +21,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
 	computed: {
 		acct() {
 			return getAcct(this.post.user);
+		},
+		name() {
+			return getUserName(this.post.user);
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/post-detail.vue b/src/client/app/mobile/views/components/post-detail.vue
index ed394acd36..0226ce081a 100644
--- a/src/client/app/mobile/views/components/post-detail.vue
+++ b/src/client/app/mobile/views/components/post-detail.vue
@@ -22,7 +22,7 @@
 			</router-link>
 			%fa:retweet%
 			<router-link class="name" :to="`/@${acct}`">
-				{{ post.user.name }}
+				{{ name }}
 			</router-link>
 			がRepost
 		</p>
@@ -33,7 +33,7 @@
 				<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
 			</router-link>
 			<div>
-				<router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link>
+				<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
 				<span class="username">@{{ pAcct }}</span>
 			</div>
 		</header>
@@ -81,6 +81,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 import parse from '../../../../../text/parse';
 
 import MkPostMenu from '../../../common/views/components/post-menu.vue';
@@ -114,9 +115,15 @@ export default Vue.extend({
 		acct(): string {
 			return getAcct(this.post.user);
 		},
+		name(): string {
+			return getUserName(this.post.user);
+		},
 		pAcct(): string {
 			return getAcct(this.p.user);
 		},
+		pName(): string {
+			return getUserName(this.p.user);
+		},
 		isRepost(): boolean {
 			return (this.post.repost &&
 				this.post.text == null &&
diff --git a/src/client/app/mobile/views/components/post-preview.vue b/src/client/app/mobile/views/components/post-preview.vue
index 81b0c22bfb..96bd4d5c15 100644
--- a/src/client/app/mobile/views/components/post-preview.vue
+++ b/src/client/app/mobile/views/components/post-preview.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 			<span class="username">@{{ acct }}</span>
 			<router-link class="time" :to="`/@${acct}/${post.id}`">
 				<mk-time :time="post.createdAt"/>
@@ -21,12 +21,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
 	computed: {
 		acct() {
 			return getAcct(this.post.user);
+		},
+		name() {
+			return getUserName(this.post.user);
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/post.sub.vue b/src/client/app/mobile/views/components/post.sub.vue
index 85ddb91880..909d5cb597 100644
--- a/src/client/app/mobile/views/components/post.sub.vue
+++ b/src/client/app/mobile/views/components/post.sub.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link>
 			<span class="username">@{{ acct }}</span>
 			<router-link class="created-at" :to="`/@${acct}/${post.id}`">
 				<mk-time :time="post.createdAt"/>
@@ -21,12 +21,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['post'],
 	computed: {
 		acct() {
 			return getAcct(this.post.user);
+		},
+		name() {
+			return getUserName(this.post.user);
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/post.vue b/src/client/app/mobile/views/components/post.vue
index 1454bc939f..eee1e80fd3 100644
--- a/src/client/app/mobile/views/components/post.vue
+++ b/src/client/app/mobile/views/components/post.vue
@@ -10,7 +10,7 @@
 			</router-link>
 			%fa:retweet%
 			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span>
-			<router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 			<span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span>
 		</p>
 		<mk-time :time="post.createdAt"/>
@@ -21,7 +21,7 @@
 		</router-link>
 		<div class="main">
 			<header>
-				<router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link>
+				<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
 				<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
 				<span class="username">@{{ pAcct }}</span>
 				<div class="info">
@@ -78,6 +78,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 import parse from '../../../../../text/parse';
 
 import MkPostMenu from '../../../common/views/components/post-menu.vue';
@@ -102,9 +103,15 @@ export default Vue.extend({
 		acct(): string {
 			return getAcct(this.post.user);
 		},
+		name(): string {
+			return getUserName(this.post.user);
+		},
 		pAcct(): string {
 			return getAcct(this.p.user);
 		},
+		pName(): string {
+			return getUserName(this.p.user);
+		},
 		isRepost(): boolean {
 			return (this.post.repost &&
 				this.post.text == null &&
diff --git a/src/client/app/mobile/views/components/ui.header.vue b/src/client/app/mobile/views/components/ui.header.vue
index 2bf47a90a9..fd4f31fd98 100644
--- a/src/client/app/mobile/views/components/ui.header.vue
+++ b/src/client/app/mobile/views/components/ui.header.vue
@@ -3,7 +3,7 @@
 	<mk-special-message/>
 	<div class="main" ref="main">
 		<div class="backdrop"></div>
-		<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p>
+		<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p>
 		<div class="content" ref="mainContainer">
 			<button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button>
 			<template v-if="hasUnreadNotifications || hasUnreadMessagingMessages || hasGameInvitations">%fa:circle%</template>
@@ -19,9 +19,15 @@
 <script lang="ts">
 import Vue from 'vue';
 import * as anime from 'animejs';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['func'],
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	},
 	data() {
 		return {
 			hasUnreadNotifications: false,
diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue
index a923774a73..61dd8ca9de 100644
--- a/src/client/app/mobile/views/components/ui.nav.vue
+++ b/src/client/app/mobile/views/components/ui.nav.vue
@@ -11,7 +11,7 @@
 		<div class="body" v-if="isOpen">
 			<router-link class="me" v-if="os.isSignedIn" :to="`/@${os.i.username}`">
 				<img class="avatar" :src="`${os.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/>
-				<p class="name">{{ os.i.name }}</p>
+				<p class="name">{{ name }}</p>
 			</router-link>
 			<div class="links">
 				<ul>
@@ -40,9 +40,15 @@
 <script lang="ts">
 import Vue from 'vue';
 import { docsUrl, chUrl, lang } from '../../../config';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['isOpen'],
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	},
 	data() {
 		return {
 			hasUnreadNotifications: false,
diff --git a/src/client/app/mobile/views/components/user-card.vue b/src/client/app/mobile/views/components/user-card.vue
index 46fa3b4732..e8698a62f0 100644
--- a/src/client/app/mobile/views/components/user-card.vue
+++ b/src/client/app/mobile/views/components/user-card.vue
@@ -5,7 +5,7 @@
 			<img :src="`${user.avatarUrl}?thumbnail&size=200`" alt="avatar"/>
 		</a>
 	</header>
-	<a class="name" :href="`/@${acct}`" target="_blank">{{ user.name }}</a>
+	<a class="name" :href="`/@${acct}`" target="_blank">{{ name }}</a>
 	<p class="username">@{{ acct }}</p>
 	<mk-follow-button :user="user"/>
 </div>
@@ -14,12 +14,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
 	computed: {
 		acct() {
 			return getAcct(this.user);
+		},
+		name() {
+			return getUserName(this.user);
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/user-preview.vue b/src/client/app/mobile/views/components/user-preview.vue
index 00f87e5549..72a6bcf8ad 100644
--- a/src/client/app/mobile/views/components/user-preview.vue
+++ b/src/client/app/mobile/views/components/user-preview.vue
@@ -5,7 +5,7 @@
 	</router-link>
 	<div class="main">
 		<header>
-			<router-link class="name" :to="`/@${acct}`">{{ user.name }}</router-link>
+			<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
 			<span class="username">@{{ acct }}</span>
 		</header>
 		<div class="body">
@@ -18,12 +18,16 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../acct/render';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
 	computed: {
 		acct() {
 			return getAcct(this.user);
+		},
+		name() {
+			return getUserName(this.user);
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/pages/followers.vue b/src/client/app/mobile/views/pages/followers.vue
index d2e6a9aea5..f4225d556d 100644
--- a/src/client/app/mobile/views/pages/followers.vue
+++ b/src/client/app/mobile/views/pages/followers.vue
@@ -2,7 +2,7 @@
 <mk-ui>
 	<template slot="header" v-if="!fetching">
 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">
-		{{ '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) }}
+		{{ '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', name) }}
 	</template>
 	<mk-users-list
 		v-if="!fetching"
@@ -20,6 +20,7 @@
 import Vue from 'vue';
 import Progress from '../../../common/scripts/loading';
 import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -28,6 +29,11 @@ export default Vue.extend({
 			user: null
 		};
 	},
+	computed: {
+		name() {
+			return getUserName(this.user);
+		}
+	},
 	watch: {
 		$route: 'fetch'
 	},
@@ -46,7 +52,7 @@ export default Vue.extend({
 				this.user = user;
 				this.fetching = false;
 
-				document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) + ' | Misskey';
+				document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', this.name) + ' | Misskey';
 			});
 		},
 		onLoaded() {
diff --git a/src/client/app/mobile/views/pages/following.vue b/src/client/app/mobile/views/pages/following.vue
index 3690536cf5..cc2442e476 100644
--- a/src/client/app/mobile/views/pages/following.vue
+++ b/src/client/app/mobile/views/pages/following.vue
@@ -2,7 +2,7 @@
 <mk-ui>
 	<template slot="header" v-if="!fetching">
 		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">
-		{{ '%i18n:mobile.tags.mk-user-following-page.following-of%'.replace('{}', user.name) }}
+		{{ '%i18n:mobile.tags.mk-user-following-page.following-of%'.replace('{}', name) }}
 	</template>
 	<mk-users-list
 		v-if="!fetching"
@@ -20,6 +20,7 @@
 import Vue from 'vue';
 import Progress from '../../../common/scripts/loading';
 import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -28,6 +29,11 @@ export default Vue.extend({
 			user: null
 		};
 	},
+	computed: {
+		name() {
+			return getUserName(this.user);
+		}
+	},
 	watch: {
 		$route: 'fetch'
 	},
@@ -46,7 +52,7 @@ export default Vue.extend({
 				this.user = user;
 				this.fetching = false;
 
-				document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) + ' | Misskey';
+				document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', this.name) + ' | Misskey';
 			});
 		},
 		onLoaded() {
diff --git a/src/client/app/mobile/views/pages/messaging-room.vue b/src/client/app/mobile/views/pages/messaging-room.vue
index 172666eea5..ae6662dc04 100644
--- a/src/client/app/mobile/views/pages/messaging-room.vue
+++ b/src/client/app/mobile/views/pages/messaging-room.vue
@@ -1,7 +1,7 @@
 <template>
 <mk-ui>
 	<span slot="header">
-		<template v-if="user">%fa:R comments%{{ user.name }}</template>
+		<template v-if="user">%fa:R comments%{{ name }}</template>
 		<template v-else><mk-ellipsis/></template>
 	</span>
 	<mk-messaging-room v-if="!fetching" :user="user" :is-naked="true"/>
@@ -11,6 +11,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -19,6 +20,11 @@ export default Vue.extend({
 			user: null
 		};
 	},
+	computed: {
+		name() {
+			return getUserName(this.user);
+		}
+	},
 	watch: {
 		$route: 'fetch'
 	},
@@ -33,7 +39,7 @@ export default Vue.extend({
 				this.user = user;
 				this.fetching = false;
 
-				document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${user.name} | Misskey`;
+				document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${this.name} | Misskey`;
 			});
 		}
 	}
diff --git a/src/client/app/mobile/views/pages/profile-setting.vue b/src/client/app/mobile/views/pages/profile-setting.vue
index 15f9bc9b68..4a560c0272 100644
--- a/src/client/app/mobile/views/pages/profile-setting.vue
+++ b/src/client/app/mobile/views/pages/profile-setting.vue
@@ -52,7 +52,7 @@ export default Vue.extend({
 		};
 	},
 	created() {
-		this.name = (this as any).os.i.name;
+		this.name = (this as any).os.i.name || '';
 		this.location = (this as any).os.i.account.profile.location;
 		this.description = (this as any).os.i.description;
 		this.birthday = (this as any).os.i.account.profile.birthday;
@@ -94,7 +94,7 @@ export default Vue.extend({
 			this.saving = true;
 
 			(this as any).api('i/update', {
-				name: this.name,
+				name: this.name || null,
 				location: this.location || null,
 				description: this.description || null,
 				birthday: this.birthday || null
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
index a945a21c5c..58a9d4e37e 100644
--- a/src/client/app/mobile/views/pages/settings.vue
+++ b/src/client/app/mobile/views/pages/settings.vue
@@ -2,7 +2,7 @@
 <mk-ui>
 	<span slot="header">%fa:cog%%i18n:mobile.tags.mk-settings-page.settings%</span>
 	<div :class="$style.content">
-		<p v-html="'%i18n:mobile.tags.mk-settings.signed-in-as%'.replace('{}', '<b>' + os.i.name + '</b>')"></p>
+		<p v-html="'%i18n:mobile.tags.mk-settings.signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p>
 		<ul>
 			<li><router-link to="./settings/profile">%fa:user%%i18n:mobile.tags.mk-settings-page.profile%%fa:angle-right%</router-link></li>
 			<li><router-link to="./settings/authorized-apps">%fa:puzzle-piece%%i18n:mobile.tags.mk-settings-page.applications%%fa:angle-right%</router-link></li>
@@ -20,6 +20,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import { version, codename } from '../../../config';
+import getUserName from '../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	data() {
@@ -28,6 +29,11 @@ export default Vue.extend({
 			codename
 		};
 	},
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	},
 	mounted() {
 		document.title = 'Misskey | %i18n:mobile.tags.mk-settings-page.settings%';
 		document.documentElement.style.background = '#313a42';
diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue
index be696484b6..3b7b4d6c28 100644
--- a/src/client/app/mobile/views/pages/user.vue
+++ b/src/client/app/mobile/views/pages/user.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-ui>
-	<span slot="header" v-if="!fetching">%fa:user% {{ user.name }}</span>
+	<span slot="header" v-if="!fetching">%fa:user% {{ user }}</span>
 	<main v-if="!fetching">
 		<header>
 			<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"></div>
@@ -12,7 +12,7 @@
 					<mk-follow-button v-if="os.isSignedIn && os.i.id != user.id" :user="user"/>
 				</div>
 				<div class="title">
-					<h1>{{ user.name }}</h1>
+					<h1>{{ user }}</h1>
 					<span class="username">@{{ acct }}</span>
 					<span class="followed" v-if="user.isFollowed">%i18n:mobile.tags.mk-user.follows-you%</span>
 				</div>
@@ -61,7 +61,7 @@
 import Vue from 'vue';
 import * as age from 's-age';
 import getAcct from '../../../../../acct/render';
-import getAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
 import Progress from '../../../common/scripts/loading';
 import XHome from './user/home.vue';
 
@@ -82,6 +82,9 @@ export default Vue.extend({
 		},
 		age(): number {
 			return age(this.user.account.profile.birthday);
+		},
+		name() {
+			return getUserName(this.user);
 		}
 	},
 	watch: {
@@ -102,7 +105,7 @@ export default Vue.extend({
 				this.fetching = false;
 
 				Progress.done();
-				document.title = user.name + ' | Misskey';
+				document.title = this.name + ' | Misskey';
 			});
 		}
 	}
diff --git a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue
index ffdd9f178d..1b128e2f24 100644
--- a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue
+++ b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue
@@ -3,7 +3,7 @@
 	<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p>
 	<div v-if="!fetching && users.length > 0">
 		<a v-for="user in users" :key="user.id" :href="`/@${getAcct(user)}`">
-			<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user.name"/>
+			<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)"/>
 		</a>
 	</div>
 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p>
@@ -13,6 +13,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import getAcct from '../../../../../../acct/render';
+import getUserName from '../../../../../../renderers/get-user-name';
 
 export default Vue.extend({
 	props: ['user'],
@@ -22,6 +23,11 @@ export default Vue.extend({
 			users: []
 		};
 	},
+	computed: {
+		name() {
+			return getUserName(this.user);
+		}
+	},
 	methods: {
 		getAcct
 	},
diff --git a/src/client/app/mobile/views/widgets/profile.vue b/src/client/app/mobile/views/widgets/profile.vue
index f1d283e45a..bd257a3ff3 100644
--- a/src/client/app/mobile/views/widgets/profile.vue
+++ b/src/client/app/mobile/views/widgets/profile.vue
@@ -8,15 +8,23 @@
 			:src="`${os.i.avatarUrl}?thumbnail&size=96`"
 			alt="avatar"
 		/>
-		<router-link :class="$style.name" :to="`/@${os.i.username}`">{{ os.i.name }}</router-link>
+		<router-link :class="$style.name" :to="`/@${os.i.username}`">{{ name }}</router-link>
 	</mk-widget-container>
 </div>
 </template>
 
 <script lang="ts">
 import define from '../../../common/define-widget';
+import getUserName from '../../../../../renderers/get-user-name';
+
 export default define({
 	name: 'profile'
+}).extend({
+	computed: {
+		name() {
+			return getUserName(this.os.i);
+		}
+	}
 });
 </script>
 
diff --git a/src/following/distribute.ts b/src/following/distribute.ts
deleted file mode 100644
index 10ff988814..0000000000
--- a/src/following/distribute.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import User, { pack as packUser } from '../models/user';
-import FollowingLog from '../models/following-log';
-import FollowedLog from '../models/followed-log';
-import event from '../publishers/stream';
-import notify from '../publishers/notify';
-
-export default async (follower, followee) => Promise.all([
-	// Increment following count
-	User.update(follower._id, {
-		$inc: {
-			followingCount: 1
-		}
-	}),
-
-	FollowingLog.insert({
-		createdAt: new Date(),
-		userId: followee._id,
-		count: follower.followingCount + 1
-	}),
-
-	// Increment followers count
-	User.update({ _id: followee._id }, {
-		$inc: {
-			followersCount: 1
-		}
-	}),
-
-	FollowedLog.insert({
-		createdAt: new Date(),
-		userId: follower._id,
-		count: followee.followersCount + 1
-	}),
-
-	followee.host === null && Promise.all([
-		// Notify
-		notify(followee.id, follower.id, 'follow'),
-
-		// Publish follow event
-		packUser(follower, followee)
-			.then(packed => event(followee._id, 'followed', packed))
-	])
-]);
diff --git a/src/index.ts b/src/index.ts
index 21fb2f5530..68b289793b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -30,8 +30,12 @@ const ev = new Xev();
 
 process.title = 'Misskey';
 
+if (process.env.NODE_ENV != 'production') {
+	process.env.DEBUG = 'misskey:*';
+}
+
 // https://github.com/Automattic/kue/issues/822
-require('events').EventEmitter.prototype._maxListeners = 256;
+require('events').EventEmitter.prototype._maxListeners = 512;
 
 // Start app
 main();
@@ -99,7 +103,7 @@ async function workerMain(opt) {
 
 	if (!opt['only-server']) {
 		// start processor
-		require('./queue').process();
+		require('./queue').default();
 	}
 
 	// Send a 'ready' message to parent process
diff --git a/src/models/post-watching.ts b/src/models/post-watching.ts
index b4ddcaafa6..032b9d10fa 100644
--- a/src/models/post-watching.ts
+++ b/src/models/post-watching.ts
@@ -2,6 +2,7 @@ import * as mongo from 'mongodb';
 import db from '../db/mongodb';
 
 const PostWatching = db.get<IPostWatching>('postWatching');
+PostWatching.createIndex(['userId', 'postId'], { unique: true });
 export default PostWatching;
 
 export interface IPostWatching {
diff --git a/src/models/post.ts b/src/models/post.ts
index 2f2b51b946..ac7890d2e6 100644
--- a/src/models/post.ts
+++ b/src/models/post.ts
@@ -27,6 +27,7 @@ export type IPost = {
 	_id: mongo.ObjectID;
 	channelId: mongo.ObjectID;
 	createdAt: Date;
+	deletedAt: Date;
 	mediaIds: mongo.ObjectID[];
 	replyId: mongo.ObjectID;
 	repostId: mongo.ObjectID;
@@ -52,6 +53,20 @@ export type IPost = {
 		speed: number;
 	};
 	uri: string;
+
+	_reply?: {
+		userId: mongo.ObjectID;
+	};
+	_repost?: {
+		userId: mongo.ObjectID;
+	};
+	_user: {
+		host: string;
+		hostLower: string;
+		account: {
+			inbox?: string;
+		};
+	};
 };
 
 /**
diff --git a/src/models/user.ts b/src/models/user.ts
index f817c33aa2..92091c6879 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -21,7 +21,7 @@ type IUserBase = {
 	deletedAt: Date;
 	followersCount: number;
 	followingCount: number;
-	name: string;
+	name?: string;
 	postsCount: number;
 	driveCapacity: number;
 	username: string;
@@ -99,8 +99,8 @@ export function validatePassword(password: string): boolean {
 	return typeof password == 'string' && password != '';
 }
 
-export function isValidName(name: string): boolean {
-	return typeof name == 'string' && name.length < 30 && name.trim() != '';
+export function isValidName(name?: string): boolean {
+	return name === null || (typeof name == 'string' && name.length < 30 && name.trim() != '');
 }
 
 export function isValidDescription(description: string): boolean {
diff --git a/src/othello/ai/back.ts b/src/othello/ai/back.ts
index d6704b1750..4d06ed9565 100644
--- a/src/othello/ai/back.ts
+++ b/src/othello/ai/back.ts
@@ -9,6 +9,7 @@
 import * as request from 'request-promise-native';
 import Othello, { Color } from '../core';
 import conf from '../../config';
+import getUserName from '../../renderers/get-user-name';
 
 let game;
 let form;
@@ -47,8 +48,8 @@ process.on('message', async msg => {
 		const user = game.user1Id == id ? game.user2 : game.user1;
 		const isSettai = form[0].value === 0;
 		const text = isSettai
-			? `?[${user.name}](${conf.url}/@${user.username})さんの接待を始めました!`
-			: `対局を?[${user.name}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`;
+			? `?[${getUserName(user)}](${conf.url}/@${user.username})さんの接待を始めました!`
+			: `対局を?[${getUserName(user)}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`;
 
 		const res = await request.post(`${conf.api_url}/posts/create`, {
 			json: { i,
@@ -72,15 +73,15 @@ process.on('message', async msg => {
 		const isSettai = form[0].value === 0;
 		const text = isSettai
 			? msg.body.winnerId === null
-				? `?[${user.name}](${conf.url}/@${user.username})さんに接待で引き分けました...`
+				? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で引き分けました...`
 				: msg.body.winnerId == id
-					? `?[${user.name}](${conf.url}/@${user.username})さんに接待で勝ってしまいました...`
-					: `?[${user.name}](${conf.url}/@${user.username})さんに接待で負けてあげました♪`
+					? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で勝ってしまいました...`
+					: `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で負けてあげました♪`
 			: msg.body.winnerId === null
-				? `?[${user.name}](${conf.url}/@${user.username})さんと引き分けました~`
+				? `?[${getUserName(user)}](${conf.url}/@${user.username})さんと引き分けました~`
 				: msg.body.winnerId == id
-					? `?[${user.name}](${conf.url}/@${user.username})さんに勝ちました♪`
-					: `?[${user.name}](${conf.url}/@${user.username})さんに負けました...`;
+					? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに勝ちました♪`
+					: `?[${getUserName(user)}](${conf.url}/@${user.username})さんに負けました...`;
 
 		await request.post(`${conf.api_url}/posts/create`, {
 			json: { i,
diff --git a/src/post/create.ts b/src/post/create.ts
deleted file mode 100644
index 4ad1503e0f..0000000000
--- a/src/post/create.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import Post from '../models/post';
-
-export default async (post, reply, repost, mentions) => {
-	post.mentions = [];
-
-	function addMention(mentionee) {
-		// Reject if already added
-		if (post.mentions.some(x => x.equals(mentionee))) return;
-
-		// Add mention
-		post.mentions.push(mentionee);
-	}
-
-	if (reply) {
-		// Add mention
-		addMention(reply.userId);
-		post.replyId = reply._id;
-		post._reply = { userId: reply.userId };
-	} else {
-		post.replyId = null;
-		post._reply = null;
-	}
-
-	if (repost) {
-		if (post.text) {
-			// Add mention
-			addMention(repost.userId);
-		}
-
-		post.repostId = repost._id;
-		post._repost = { userId: repost.userId };
-	} else {
-		post.repostId = null;
-		post._repost = null;
-	}
-
-	await Promise.all(mentions.map(({ _id }) => addMention(_id)));
-
-	return Post.insert(post);
-};
diff --git a/src/post/distribute.ts b/src/post/distribute.ts
deleted file mode 100644
index f748a620c0..0000000000
--- a/src/post/distribute.ts
+++ /dev/null
@@ -1,274 +0,0 @@
-import Channel from '../models/channel';
-import ChannelWatching from '../models/channel-watching';
-import Following from '../models/following';
-import Mute from '../models/mute';
-import Post, { pack } from '../models/post';
-import Watching from '../models/post-watching';
-import User, { isLocalUser } from '../models/user';
-import stream, { publishChannelStream } from '../publishers/stream';
-import notify from '../publishers/notify';
-import pushSw from '../publishers/push-sw';
-import { createHttp } from '../queue';
-import watch from './watch';
-
-export default async (user, mentions, post) => {
-	const promisedPostObj = pack(post);
-	const promises = [
-		User.update({ _id: user._id }, {
-			// Increment my posts count
-			$inc: {
-				postsCount: 1
-			},
-
-			$set: {
-				latestPost: post._id
-			}
-		}),
-	] as Array<Promise<any>>;
-
-	function addMention(promisedMentionee, reason) {
-		// Publish event
-		promises.push(promisedMentionee.then(mentionee => {
-			if (user._id.equals(mentionee)) {
-				return Promise.resolve();
-			}
-
-			return Promise.all([
-				promisedPostObj,
-				Mute.find({
-					muterId: mentionee,
-					deletedAt: { $exists: false }
-				})
-			]).then(([postObj, mentioneeMutes]) => {
-				const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId.toString());
-				if (mentioneesMutedUserIds.indexOf(user._id.toString()) == -1) {
-					stream(mentionee, reason, postObj);
-					pushSw(mentionee, reason, postObj);
-				}
-			});
-		}));
-	}
-
-	// タイムラインへの投稿
-	if (!post.channelId) {
-		promises.push(
-			// Publish event to myself's stream
-			promisedPostObj.then(postObj => {
-				stream(post.userId, 'post', postObj);
-			}),
-
-			Promise.all([
-				User.findOne({ _id: post.userId }),
-
-				// Fetch all followers
-				Following.aggregate([{
-					$lookup: {
-						from: 'users',
-						localField: 'followerId',
-						foreignField: '_id',
-						as: 'follower'
-					}
-				}, {
-					$match: {
-						followeeId: post.userId
-					}
-				}], {
-					_id: false
-				})
-			]).then(([user, followers]) => Promise.all(followers.map(following => {
-				if (isLocalUser(following.follower)) {
-					// Publish event to followers stream
-					return promisedPostObj.then(postObj => {
-						stream(following.followerId, 'post', postObj);
-					});
-				}
-
-				return new Promise((resolve, reject) => {
-					createHttp({
-						type: 'deliverPost',
-						fromId: user._id,
-						toId: following.followerId,
-						postId: post._id
-					}).save(error => {
-						if (error) {
-							reject(error);
-						} else {
-							resolve();
-						}
-					});
-				});
-			})))
-		);
-	}
-
-	// チャンネルへの投稿
-	if (post.channelId) {
-		promises.push(
-			// Increment channel index(posts count)
-			Channel.update({ _id: post.channelId }, {
-				$inc: {
-					index: 1
-				}
-			}),
-
-			// Publish event to channel
-			promisedPostObj.then(postObj => {
-				publishChannelStream(post.channelId, 'post', postObj);
-			}),
-
-			Promise.all([
-				promisedPostObj,
-
-				// Get channel watchers
-				ChannelWatching.find({
-					channelId: post.channelId,
-					// 削除されたドキュメントは除く
-					deletedAt: { $exists: false }
-				})
-			]).then(([postObj, watches]) => {
-				// チャンネルの視聴者(のタイムライン)に配信
-				watches.forEach(w => {
-					stream(w.userId, 'post', postObj);
-				});
-			})
-		);
-	}
-
-	// If has in reply to post
-	if (post.replyId) {
-		promises.push(
-			// Increment replies count
-			Post.update({ _id: post.replyId }, {
-				$inc: {
-					repliesCount: 1
-				}
-			}),
-
-			// 自分自身へのリプライでない限りは通知を作成
-			promisedPostObj.then(({ reply }) => {
-				return notify(reply.userId, user._id, 'reply', {
-					postId: post._id
-				});
-			}),
-
-			// Fetch watchers
-			Watching
-				.find({
-					postId: post.replyId,
-					userId: { $ne: user._id },
-					// 削除されたドキュメントは除く
-					deletedAt: { $exists: false }
-				}, {
-					fields: {
-						userId: true
-					}
-				})
-				.then(watchers => {
-					watchers.forEach(watcher => {
-						notify(watcher.userId, user._id, 'reply', {
-							postId: post._id
-						});
-					});
-				})
-		);
-
-		// Add mention
-		addMention(promisedPostObj.then(({ reply }) => reply.userId), 'reply');
-
-		// この投稿をWatchする
-		if (user.account.settings.autoWatch !== false) {
-			promises.push(promisedPostObj.then(({ reply }) => {
-				return watch(user._id, reply);
-			}));
-		}
-	}
-
-	// If it is repost
-	if (post.repostId) {
-		const type = post.text ? 'quote' : 'repost';
-
-		promises.push(
-			promisedPostObj.then(({ repost }) => Promise.all([
-				// Notify
-				notify(repost.userId, user._id, type, {
-					postId: post._id
-				}),
-
-				// この投稿をWatchする
-				// TODO: ユーザーが「Repostしたときに自動でWatchする」設定を
-				//       オフにしていた場合はしない
-				watch(user._id, repost)
-			])),
-
-			// Fetch watchers
-			Watching
-				.find({
-					postId: post.repostId,
-					userId: { $ne: user._id },
-					// 削除されたドキュメントは除く
-					deletedAt: { $exists: false }
-				}, {
-					fields: {
-						userId: true
-					}
-				})
-				.then(watchers => {
-					watchers.forEach(watcher => {
-						notify(watcher.userId, user._id, type, {
-							postId: post._id
-						});
-					});
-				})
-		);
-
-		// If it is quote repost
-		if (post.text) {
-			// Add mention
-			addMention(promisedPostObj.then(({ repost }) => repost.userId), 'quote');
-		} else {
-			promises.push(promisedPostObj.then(postObj => {
-				// Publish event
-				if (!user._id.equals(postObj.repost.userId)) {
-					stream(postObj.repost.userId, 'repost', postObj);
-				}
-			}));
-		}
-
-		// 今までで同じ投稿をRepostしているか
-		const existRepost = await Post.findOne({
-			userId: user._id,
-			repostId: post.repostId,
-			_id: {
-				$ne: post._id
-			}
-		});
-
-		if (!existRepost) {
-			// Update repostee status
-			promises.push(Post.update({ _id: post.repostId }, {
-				$inc: {
-					repostCount: 1
-				}
-			}));
-		}
-	}
-
-	// Resolve all mentions
-	await promisedPostObj.then(({ reply, repost }) => Promise.all(mentions.map(async mention => {
-		// 既に言及されたユーザーに対する返信や引用repostの場合も無視
-		if (reply && reply.userId.equals(mention)) return;
-		if (repost && repost.userId.equals(mention)) return;
-
-		// Add mention
-		addMention(mention, 'mention');
-
-		// Create notification
-		await notify(mention, user._id, 'mention', {
-			postId: post._id
-		});
-	})));
-
-	await Promise.all(promises);
-
-	return promisedPostObj;
-};
diff --git a/src/queue/index.ts b/src/queue/index.ts
index f90754a561..4aa1dc032d 100644
--- a/src/queue/index.ts
+++ b/src/queue/index.ts
@@ -1,6 +1,6 @@
 import { createQueue } from 'kue';
+
 import config from '../config';
-import db from './processors/db';
 import http from './processors/http';
 
 const queue = createQueue({
@@ -18,17 +18,19 @@ export function createHttp(data) {
 		.backoff({ delay: 16384, type: 'exponential' });
 }
 
-export function createDb(data) {
-	return queue.create('db', data);
+export function deliver(user, content, to) {
+	return createHttp({
+		type: 'deliver',
+		user,
+		content,
+		to
+	});
 }
 
-export function process() {
-	queue.process('db', db);
-
+export default function() {
 	/*
 		256 is the default concurrency limit of Mozilla Firefox and Google
 		Chromium.
-
 		a8af215e691f3a2205a3758d2d96e9d328e100ff - chromium/src.git - Git at Google
 		https://chromium.googlesource.com/chromium/src.git/+/a8af215e691f3a2205a3758d2d96e9d328e100ff
 		Network.http.max-connections - MozillaZine Knowledge Base
diff --git a/src/queue/processors/db/delete-post-dependents.ts b/src/queue/processors/db/delete-post-dependents.ts
deleted file mode 100644
index fb6617e952..0000000000
--- a/src/queue/processors/db/delete-post-dependents.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import Favorite from '../../../models/favorite';
-import Notification from '../../../models/notification';
-import PollVote from '../../../models/poll-vote';
-import PostReaction from '../../../models/post-reaction';
-import PostWatching from '../../../models/post-watching';
-import Post from '../../../models/post';
-
-export default ({ data }, done) => Promise.all([
-	Favorite.remove({ postId: data._id }),
-	Notification.remove({ postId: data._id }),
-	PollVote.remove({ postId: data._id }),
-	PostReaction.remove({ postId: data._id }),
-	PostWatching.remove({ postId: data._id }),
-	Post.find({ repostId: data._id }).then(reposts => Promise.all([
-		Notification.remove({
-			postId: {
-				$in: reposts.map(({ _id }) => _id)
-			}
-		}),
-		Post.remove({ repostId: data._id })
-	]))
-]).then(() => done(), done);
diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts
deleted file mode 100644
index 468ec442ac..0000000000
--- a/src/queue/processors/db/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import deletePostDependents from './delete-post-dependents';
-
-const handlers = {
-  deletePostDependents
-};
-
-export default (job, done) => handlers[job.data.type](job, done);
diff --git a/src/queue/processors/http/deliver-post.ts b/src/queue/processors/http/deliver-post.ts
deleted file mode 100644
index 8107c8bf74..0000000000
--- a/src/queue/processors/http/deliver-post.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import Post from '../../../models/post';
-import User, { IRemoteUser } from '../../../models/user';
-import context from '../../../remote/activitypub/renderer/context';
-import renderCreate from '../../../remote/activitypub/renderer/create';
-import renderNote from '../../../remote/activitypub/renderer/note';
-import request from '../../../remote/request';
-
-export default async ({ data }, done) => {
-	try {
-		const promisedTo = User.findOne({ _id: data.toId }) as Promise<IRemoteUser>;
-		const [from, post] = await Promise.all([
-			User.findOne({ _id: data.fromId }),
-			Post.findOne({ _id: data.postId })
-		]);
-		const note = await renderNote(from, post);
-		const to = await promisedTo;
-		const create = renderCreate(note);
-
-		create['@context'] = context;
-
-		await request(from, to.account.inbox, create);
-	} catch (error) {
-		done(error);
-	}
-
-	done();
-};
diff --git a/src/queue/processors/http/deliver.ts b/src/queue/processors/http/deliver.ts
new file mode 100644
index 0000000000..422e355b5f
--- /dev/null
+++ b/src/queue/processors/http/deliver.ts
@@ -0,0 +1,19 @@
+import * as kue from 'kue';
+
+import request from '../../../remote/request';
+
+export default async (job: kue.Job, done): Promise<void> => {
+	try {
+		await request(job.data.user, job.data.to, job.data.content);
+		done();
+	} catch (res) {
+		if (res.statusCode >= 400 && res.statusCode < 500) {
+			// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
+			// 何回再送しても成功することはないということなのでエラーにはしないでおく
+			done();
+		} else {
+			console.warn(`deliver failed: ${res.statusMessage}`);
+			done(new Error(res.statusMessage));
+		}
+	}
+};
diff --git a/src/queue/processors/http/follow.ts b/src/queue/processors/http/follow.ts
deleted file mode 100644
index ba1cc31186..0000000000
--- a/src/queue/processors/http/follow.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../../models/user';
-import Following from '../../../models/following';
-import FollowingLog from '../../../models/following-log';
-import FollowedLog from '../../../models/followed-log';
-import event from '../../../publishers/stream';
-import notify from '../../../publishers/notify';
-import context from '../../../remote/activitypub/renderer/context';
-import render from '../../../remote/activitypub/renderer/follow';
-import request from '../../../remote/request';
-
-export default ({ data }, done) => Following.findOne({ _id: data.following }).then(async ({ followerId, followeeId }) => {
-	const [follower, followee] = await Promise.all([
-		User.findOne({ _id: followerId }),
-		User.findOne({ _id: followeeId })
-	]);
-
-	if (isLocalUser(follower) && isRemoteUser(followee)) {
-		const rendered = render(follower, followee);
-		rendered['@context'] = context;
-
-		await request(follower, followee.account.inbox, rendered);
-	}
-
-	return [follower, followee];
-}).then(([follower, followee]) => Promise.all([
-	// Increment following count
-	User.update(follower._id, {
-		$inc: {
-			followingCount: 1
-		}
-	}),
-
-	FollowingLog.insert({
-		createdAt: data.following.createdAt,
-		userId: follower._id,
-		count: follower.followingCount + 1
-	}),
-
-	// Increment followers count
-	User.update({ _id: followee._id }, {
-		$inc: {
-			followersCount: 1
-		}
-	}),
-
-	FollowedLog.insert({
-		createdAt: data.following.createdAt,
-		userId: follower._id,
-		count: followee.followersCount + 1
-	}),
-
-	// Publish follow event
-	isLocalUser(follower) && packUser(followee, follower)
-		.then(packed => event(follower._id, 'follow', packed)),
-
-	isLocalUser(followee) && Promise.all([
-		packUser(follower, followee)
-			.then(packed => event(followee._id, 'followed', packed)),
-
-		// Notify
-		isLocalUser(followee) && notify(followee._id, follower._id, 'follow')
-	])
-]).then(() => done(), error => {
-	done();
-	throw error;
-}), done);
diff --git a/src/queue/processors/http/index.ts b/src/queue/processors/http/index.ts
index 0ea79305c6..3dc2595374 100644
--- a/src/queue/processors/http/index.ts
+++ b/src/queue/processors/http/index.ts
@@ -1,17 +1,20 @@
-import deliverPost from './deliver-post';
-import follow from './follow';
-import performActivityPub from './perform-activitypub';
+import deliver from './deliver';
 import processInbox from './process-inbox';
 import reportGitHubFailure from './report-github-failure';
-import unfollow from './unfollow';
 
 const handlers = {
-  deliverPost,
-  follow,
-  performActivityPub,
-  processInbox,
-  reportGitHubFailure,
-  unfollow
+	deliver,
+	processInbox,
+	reportGitHubFailure
 };
 
-export default (job, done) => handlers[job.data.type](job, done);
+export default (job, done) => {
+	const handler = handlers[job.data.type];
+
+	if (handler) {
+		handler(job, done);
+	} else {
+		console.error(`Unknown job: ${job.data.type}`);
+		done();
+	}
+};
diff --git a/src/queue/processors/http/perform-activitypub.ts b/src/queue/processors/http/perform-activitypub.ts
deleted file mode 100644
index ae70c0f0be..0000000000
--- a/src/queue/processors/http/perform-activitypub.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import User from '../../../models/user';
-import act from '../../../remote/activitypub/act';
-import Resolver from '../../../remote/activitypub/resolver';
-
-export default ({ data }, done) => User.findOne({ _id: data.actor })
-	.then(actor => act(new Resolver(), actor, data.outbox))
-	.then(Promise.all)
-	.then(() => done(), done);
diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts
index 7eeaa19f8a..eb4b62d37f 100644
--- a/src/queue/processors/http/process-inbox.ts
+++ b/src/queue/processors/http/process-inbox.ts
@@ -1,44 +1,66 @@
+import * as kue from 'kue';
+import * as debug from 'debug';
+
 import { verifySignature } from 'http-signature';
 import parseAcct from '../../../acct/parse';
 import User, { IRemoteUser } from '../../../models/user';
 import act from '../../../remote/activitypub/act';
 import resolvePerson from '../../../remote/activitypub/resolve-person';
-import Resolver from '../../../remote/activitypub/resolver';
 
-export default async ({ data }, done) => {
-	try {
-		const keyIdLower = data.signature.keyId.toLowerCase();
-		let user;
+const log = debug('misskey:queue:inbox');
 
-		if (keyIdLower.startsWith('acct:')) {
-			const { username, host } = parseAcct(keyIdLower.slice('acct:'.length));
-			if (host === null) {
-				done();
-				return;
-			}
+// ユーザーのinboxにアクティビティが届いた時の処理
+export default async (job: kue.Job, done): Promise<void> => {
+	const signature = job.data.signature;
+	const activity = job.data.activity;
 
-			user = await User.findOne({ usernameLower: username, hostLower: host }) as IRemoteUser;
-		} else {
-			user = await User.findOne({
-				host: { $ne: null },
-				'account.publicKey.id': data.signature.keyId
-			}) as IRemoteUser;
+	//#region Log
+	const info = Object.assign({}, activity);
+	delete info['@context'];
+	delete info['signature'];
+	log(info);
+	//#endregion
 
-			if (user === null) {
-				user = await resolvePerson(new Resolver(), data.signature.keyId);
-			}
-		}
+	const keyIdLower = signature.keyId.toLowerCase();
+	let user;
 
-		if (user === null || !verifySignature(data.signature, user.account.publicKey.publicKeyPem)) {
+	if (keyIdLower.startsWith('acct:')) {
+		const { username, host } = parseAcct(keyIdLower.slice('acct:'.length));
+		if (host === null) {
+			console.warn(`request was made by local user: @${username}`);
 			done();
 			return;
 		}
 
-		await Promise.all(await act(new Resolver(), user, data.inbox, true));
-	} catch (error) {
-		done(error);
+		user = await User.findOne({ usernameLower: username, hostLower: host }) as IRemoteUser;
+	} else {
+		user = await User.findOne({
+			host: { $ne: null },
+			'account.publicKey.id': signature.keyId
+		}) as IRemoteUser;
+
+		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
+		if (user === null) {
+			user = await resolvePerson(signature.keyId);
+		}
+	}
+
+	if (user === null) {
+		done(new Error('failed to resolve user'));
 		return;
 	}
 
-	done();
+	if (!verifySignature(signature, user.account.publicKey.publicKeyPem)) {
+		console.warn('signature verification failed');
+		done();
+		return;
+	}
+
+	// アクティビティを処理
+	try {
+		await act(user, activity);
+		done();
+	} catch (e) {
+		done(e);
+	}
 };
diff --git a/src/queue/processors/http/report-github-failure.ts b/src/queue/processors/http/report-github-failure.ts
index af9659bdac..1e0b51f89f 100644
--- a/src/queue/processors/http/report-github-failure.ts
+++ b/src/queue/processors/http/report-github-failure.ts
@@ -1,31 +1,24 @@
 import * as request from 'request-promise-native';
 import User from '../../../models/user';
-const createPost = require('../../../server/api/endpoints/posts/create');
+import createPost from '../../../services/post/create';
 
-export default async ({ data }, done) => {
-	try {
-		const asyncBot = User.findOne({ _id: data.userId });
+export default async ({ data }) => {
+	const asyncBot = User.findOne({ _id: data.userId });
 
-		// Fetch parent status
-		const parentStatuses = await request({
-			url: `${data.parentUrl}/statuses`,
-			headers: {
-				'User-Agent': 'misskey'
-			},
-			json: true
-		});
+	// Fetch parent status
+	const parentStatuses = await request({
+		url: `${data.parentUrl}/statuses`,
+		headers: {
+			'User-Agent': 'misskey'
+		},
+		json: true
+	});
 
-		const parentState = parentStatuses[0].state;
-		const stillFailed = parentState == 'failure' || parentState == 'error';
-		const text = stillFailed ?
-			`**⚠️BUILD STILL FAILED⚠️**: ?[${data.message}](${data.htmlUrl})` :
-			`**🚨BUILD FAILED🚨**: →→→?[${data.message}](${data.htmlUrl})←←←`;
+	const parentState = parentStatuses[0].state;
+	const stillFailed = parentState == 'failure' || parentState == 'error';
+	const text = stillFailed ?
+		`**⚠️BUILD STILL FAILED⚠️**: ?[${data.message}](${data.htmlUrl})` :
+		`**🚨BUILD FAILED🚨**: →→→?[${data.message}](${data.htmlUrl})←←←`;
 
-		createPost({ text }, await asyncBot);
-	} catch (error) {
-		done(error);
-		return;
-	}
-
-	done();
+	createPost(await asyncBot, { text });
 };
diff --git a/src/queue/processors/http/unfollow.ts b/src/queue/processors/http/unfollow.ts
deleted file mode 100644
index d62eb280dc..0000000000
--- a/src/queue/processors/http/unfollow.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import FollowedLog from '../../../models/followed-log';
-import Following from '../../../models/following';
-import FollowingLog from '../../../models/following-log';
-import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../../models/user';
-import stream from '../../../publishers/stream';
-import renderFollow from '../../../remote/activitypub/renderer/follow';
-import renderUndo from '../../../remote/activitypub/renderer/undo';
-import context from '../../../remote/activitypub/renderer/context';
-import request from '../../../remote/request';
-
-export default async ({ data }, done) => {
-	const following = await Following.findOne({ _id: data.id });
-	if (following === null) {
-		done();
-		return;
-	}
-
-	let follower;
-	let followee;
-
-	try {
-		[follower, followee] = await Promise.all([
-			User.findOne({ _id: following.followerId }),
-			User.findOne({ _id: following.followeeId })
-		]);
-
-		if (isLocalUser(follower) && isRemoteUser(followee)) {
-			const undo = renderUndo(renderFollow(follower, followee));
-			undo['@context'] = context;
-
-			await request(follower, followee.account.inbox, undo);
-		}
-	} catch (error) {
-		done(error);
-		return;
-	}
-
-	try {
-		await Promise.all([
-			// Delete following
-			Following.findOneAndDelete({ _id: data.id }),
-
-			// Decrement following count
-			User.update({ _id: follower._id }, { $inc: { followingCount: -1 } }),
-			FollowingLog.insert({
-				createdAt: new Date(),
-				userId: follower._id,
-				count: follower.followingCount - 1
-			}),
-
-			// Decrement followers count
-			User.update({ _id: followee._id }, { $inc: { followersCount: -1 } }),
-			FollowedLog.insert({
-				createdAt: new Date(),
-				userId: followee._id,
-				count: followee.followersCount - 1
-			})
-		]);
-
-		if (isLocalUser(follower)) {
-			return;
-		}
-
-		const promisedPackedUser = packUser(followee, follower);
-
-		// Publish follow event
-		stream(follower._id, 'unfollow', promisedPackedUser);
-	} finally {
-		done();
-	}
-};
diff --git a/src/remote/activitypub/act/create.ts b/src/remote/activitypub/act/create.ts
deleted file mode 100644
index fa681982cf..0000000000
--- a/src/remote/activitypub/act/create.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import create from '../create';
-import Resolver from '../resolver';
-
-export default (resolver: Resolver, actor, activity, distribute) => {
-	if ('actor' in activity && actor.account.uri !== activity.actor) {
-		throw new Error();
-	}
-
-	return create(resolver, actor, activity.object, distribute);
-};
diff --git a/src/remote/activitypub/act/create/image.ts b/src/remote/activitypub/act/create/image.ts
new file mode 100644
index 0000000000..30a75e7377
--- /dev/null
+++ b/src/remote/activitypub/act/create/image.ts
@@ -0,0 +1,18 @@
+import * as debug from 'debug';
+
+import uploadFromUrl from '../../../../services/drive/upload-from-url';
+import { IRemoteUser } from '../../../../models/user';
+import { IDriveFile } from '../../../../models/drive-file';
+
+const log = debug('misskey:activitypub');
+
+export default async function(actor: IRemoteUser, image): Promise<IDriveFile> {
+	if ('attributedTo' in image && actor.account.uri !== image.attributedTo) {
+		log(`invalid image: ${JSON.stringify(image, null, 2)}`);
+		throw new Error('invalid image');
+	}
+
+	log(`Creating the Image: ${image.id}`);
+
+	return await uploadFromUrl(image.url, actor);
+}
diff --git a/src/remote/activitypub/act/create/index.ts b/src/remote/activitypub/act/create/index.ts
new file mode 100644
index 0000000000..dd0b112141
--- /dev/null
+++ b/src/remote/activitypub/act/create/index.ts
@@ -0,0 +1,44 @@
+import * as debug from 'debug';
+
+import Resolver from '../../resolver';
+import { IRemoteUser } from '../../../../models/user';
+import createNote from './note';
+import createImage from './image';
+import { ICreate } from '../../type';
+
+const log = debug('misskey:activitypub');
+
+export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
+	if ('actor' in activity && actor.account.uri !== activity.actor) {
+		throw new Error('invalid actor');
+	}
+
+	const uri = activity.id || activity;
+
+	log(`Create: ${uri}`);
+
+	const resolver = new Resolver();
+
+	let object;
+
+	try {
+		object = await resolver.resolve(activity.object);
+	} catch (e) {
+		log(`Resolution failed: ${e}`);
+		throw e;
+	}
+
+	switch (object.type) {
+	case 'Image':
+		createImage(actor, object);
+		break;
+
+	case 'Note':
+		createNote(resolver, actor, object);
+		break;
+
+	default:
+		console.warn(`Unknown type: ${object.type}`);
+		break;
+	}
+};
diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts
new file mode 100644
index 0000000000..82a6207038
--- /dev/null
+++ b/src/remote/activitypub/act/create/note.ts
@@ -0,0 +1,89 @@
+import { JSDOM } from 'jsdom';
+import * as debug from 'debug';
+
+import Resolver from '../../resolver';
+import Post, { IPost } from '../../../../models/post';
+import createPost from '../../../../services/post/create';
+import { IRemoteUser } from '../../../../models/user';
+import resolvePerson from '../../resolve-person';
+import createImage from './image';
+import config from '../../../../config';
+
+const log = debug('misskey:activitypub');
+
+/**
+ * 投稿作成アクティビティを捌きます
+ */
+export default async function createNote(resolver: Resolver, actor: IRemoteUser, note, silent = false): Promise<IPost> {
+	if (typeof note.id !== 'string') {
+		log(`invalid note: ${JSON.stringify(note, null, 2)}`);
+		throw new Error('invalid note');
+	}
+
+	// 既に同じURIを持つものが登録されていないかチェックし、登録されていたらそれを返す
+	const exist = await Post.findOne({ uri: note.id });
+	if (exist) {
+		return exist;
+	}
+
+	log(`Creating the Note: ${note.id}`);
+
+	//#region Visibility
+	let visibility = 'public';
+	if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted';
+	if (note.cc.length == 0) visibility = 'private';
+	// TODO
+	if (visibility != 'public') throw new Error('unspported visibility');
+	//#endergion
+
+	//#region 添付メディア
+	const media = [];
+	if ('attachment' in note && note.attachment != null) {
+		// TODO: attachmentは必ずしもImageではない
+		// TODO: attachmentは必ずしも配列ではない
+		// TODO: ループの中でawaitはすべきでない
+		note.attachment.forEach(async media => {
+			const created = await createImage(note.actor, media);
+			media.push(created);
+		});
+	}
+	//#endregion
+
+	//#region リプライ
+	let reply = null;
+	if ('inReplyTo' in note && note.inReplyTo != null) {
+		// リプライ先の投稿がMisskeyに登録されているか調べる
+		const uri: string = note.inReplyTo.id || note.inReplyTo;
+		const inReplyToPost = uri.startsWith(config.url + '/')
+			? await Post.findOne({ _id: uri.split('/').pop() })
+			: await Post.findOne({ uri });
+
+		if (inReplyToPost) {
+			reply = inReplyToPost;
+		} else {
+			// 無かったらフェッチ
+			const inReplyTo = await resolver.resolve(note.inReplyTo) as any;
+
+			// リプライ先の投稿の投稿者をフェッチ
+			const actor = await resolvePerson(inReplyTo.attributedTo) as IRemoteUser;
+
+			// TODO: silentを常にtrueにしてはならない
+			reply = await createNote(resolver, actor, inReplyTo);
+		}
+	}
+	//#endregion
+
+	const { window } = new JSDOM(note.content);
+
+	return await createPost(actor, {
+		createdAt: new Date(note.published),
+		media,
+		reply,
+		repost: undefined,
+		text: window.document.body.textContent,
+		viaMobile: false,
+		geo: undefined,
+		visibility,
+		uri: note.id
+	});
+}
diff --git a/src/remote/activitypub/act/delete.ts b/src/remote/activitypub/act/delete.ts
deleted file mode 100644
index f9eb4dd08d..0000000000
--- a/src/remote/activitypub/act/delete.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import create from '../create';
-import deleteObject from '../delete';
-
-export default async (resolver, actor, activity) => {
-	if ('actor' in activity && actor.account.uri !== activity.actor) {
-		throw new Error();
-	}
-
-	const results = await create(resolver, actor, activity.object);
-
-	await Promise.all(results.map(async promisedResult => {
-		const result = await promisedResult;
-		if (result === null) {
-			return;
-		}
-
-		await deleteObject(result);
-	}));
-
-	return null;
-};
diff --git a/src/remote/activitypub/act/delete/index.ts b/src/remote/activitypub/act/delete/index.ts
new file mode 100644
index 0000000000..e34577b310
--- /dev/null
+++ b/src/remote/activitypub/act/delete/index.ts
@@ -0,0 +1,36 @@
+import Resolver from '../../resolver';
+import deleteNote from './note';
+import Post from '../../../../models/post';
+import { IRemoteUser } from '../../../../models/user';
+
+/**
+ * 削除アクティビティを捌きます
+ */
+export default async (actor: IRemoteUser, activity): Promise<void> => {
+	if ('actor' in activity && actor.account.uri !== activity.actor) {
+		throw new Error('invalid actor');
+	}
+
+	const resolver = new Resolver();
+
+	const object = await resolver.resolve(activity.object);
+
+	const uri = (object as any).id;
+
+	switch (object.type) {
+	case 'Note':
+		deleteNote(actor, uri);
+		break;
+
+	case 'Tombstone':
+		const post = await Post.findOne({ uri });
+		if (post != null) {
+			deleteNote(actor, uri);
+		}
+		break;
+
+	default:
+		console.warn(`Unknown type: ${object.type}`);
+		break;
+	}
+};
diff --git a/src/remote/activitypub/act/delete/note.ts b/src/remote/activitypub/act/delete/note.ts
new file mode 100644
index 0000000000..8e9447b481
--- /dev/null
+++ b/src/remote/activitypub/act/delete/note.ts
@@ -0,0 +1,30 @@
+import * as debug from 'debug';
+
+import Post from '../../../../models/post';
+import { IRemoteUser } from '../../../../models/user';
+
+const log = debug('misskey:activitypub');
+
+export default async function(actor: IRemoteUser, uri: string): Promise<void> {
+	log(`Deleting the Note: ${uri}`);
+
+	const post = await Post.findOne({ uri });
+
+	if (post == null) {
+		throw new Error('post not found');
+	}
+
+	if (!post.userId.equals(actor._id)) {
+		throw new Error('投稿を削除しようとしているユーザーは投稿の作成者ではありません');
+	}
+
+	Post.update({ _id: post._id }, {
+		$set: {
+			deletedAt: new Date(),
+			text: null,
+			textHtml: null,
+			mediaIds: [],
+			poll: null
+		}
+	});
+}
diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts
index 222a257e1a..3dd029af54 100644
--- a/src/remote/activitypub/act/follow.ts
+++ b/src/remote/activitypub/act/follow.ts
@@ -1,17 +1,12 @@
-import { MongoError } from 'mongodb';
 import parseAcct from '../../../acct/parse';
-import Following, { IFollowing } from '../../../models/following';
-import User from '../../../models/user';
+import User, { IRemoteUser } from '../../../models/user';
 import config from '../../../config';
-import { createHttp } from '../../../queue';
-import context from '../renderer/context';
-import renderAccept from '../renderer/accept';
-import request from '../../request';
-import Resolver from '../resolver';
+import follow from '../../../services/following/create';
+import { IFollow } from '../type';
 
-export default async (resolver: Resolver, actor, activity, distribute) => {
+export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 	const prefix = config.url + '/@';
-	const id = activity.object.id || activity.object;
+	const id = typeof activity == 'string' ? activity : activity.id;
 
 	if (!id.startsWith(prefix)) {
 		return null;
@@ -27,52 +22,5 @@ export default async (resolver: Resolver, actor, activity, distribute) => {
 		throw new Error();
 	}
 
-	if (!distribute) {
-		const { _id } = await Following.findOne({
-			followerId: actor._id,
-			followeeId: followee._id
-		});
-
-		return {
-			resolver,
-			object: { $ref: 'following', $id: _id }
-		};
-	}
-
-	const promisedFollowing = Following.insert({
-		createdAt: new Date(),
-		followerId: actor._id,
-		followeeId: followee._id
-	}).then(following => new Promise((resolve, reject) => {
-		createHttp({
-			type: 'follow',
-			following: following._id
-		}).save(error => {
-			if (error) {
-				reject(error);
-			} else {
-				resolve(following);
-			}
-		});
-	}) as Promise<IFollowing>, async error => {
-		// duplicate key error
-		if (error instanceof MongoError && error.code === 11000) {
-			return Following.findOne({
-				followerId: actor._id,
-				followeeId: followee._id
-			});
-		}
-
-		throw error;
-	});
-
-	const accept = renderAccept(activity);
-	accept['@context'] = context;
-
-	await request(followee, actor.account.inbox, accept);
-
-	return promisedFollowing.then(({ _id }) => ({
-		resolver,
-		object: { $ref: 'following', $id: _id }
-	}));
+	await follow(actor, followee, activity);
 };
diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts
index d282e12885..45d7bd16a9 100644
--- a/src/remote/activitypub/act/index.ts
+++ b/src/remote/activitypub/act/index.ts
@@ -1,36 +1,46 @@
+import { Object } from '../type';
+import { IRemoteUser } from '../../../models/user';
 import create from './create';
 import performDeleteActivity from './delete';
 import follow from './follow';
 import undo from './undo';
-import createObject from '../create';
-import Resolver from '../resolver';
+import like from './like';
 
-export default async (parentResolver: Resolver, actor, value, distribute?: boolean) => {
-	const collection = await parentResolver.resolveCollection(value);
+const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
+	switch (activity.type) {
+	case 'Create':
+		await create(actor, activity);
+		break;
 
-	return collection.object.map(async element => {
-		const { resolver, object } = await collection.resolver.resolveOne(element);
-		const created = await (await createObject(resolver, actor, [object], distribute))[0];
+	case 'Delete':
+		await performDeleteActivity(actor, activity);
+		break;
 
-		if (created !== null) {
-			return created;
-		}
+	case 'Follow':
+		await follow(actor, activity);
+		break;
 
-		switch (object.type) {
-		case 'Create':
-			return create(resolver, actor, object, distribute);
+	case 'Accept':
+		// noop
+		break;
 
-		case 'Delete':
-			return performDeleteActivity(resolver, actor, object);
+	case 'Like':
+		await like(actor, activity);
+		break;
 
-		case 'Follow':
-			return follow(resolver, actor, object, distribute);
+	case 'Undo':
+		await undo(actor, activity);
+		break;
 
-		case 'Undo':
-			return undo(resolver, actor, object);
+	case 'Collection':
+	case 'OrderedCollection':
+		// TODO
+		break;
 
-		default:
-			return null;
-		}
-	});
+	default:
+		console.warn(`unknown activity type: ${(activity as any).type}`);
+		return null;
+	}
 };
+
+export default self;
diff --git a/src/remote/activitypub/act/like.ts b/src/remote/activitypub/act/like.ts
index ea53242017..2f5e3f807d 100644
--- a/src/remote/activitypub/act/like.ts
+++ b/src/remote/activitypub/act/like.ts
@@ -1,10 +1,10 @@
-import { MongoError } from 'mongodb';
-import Reaction, { IPostReaction } from '../../../models/post-reaction';
 import Post from '../../../models/post';
-import queue from '../../../queue';
+import { IRemoteUser } from '../../../models/user';
+import { ILike } from '../type';
+import create from '../../../services/post/reaction/create';
 
-export default async (resolver, actor, activity, distribute) => {
-	const id = activity.object.id || activity.object;
+export default async (actor: IRemoteUser, activity: ILike) => {
+	const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
 
 	// Transform:
 	// https://misskey.ex/@syuilo/xxxx to
@@ -16,48 +16,5 @@ export default async (resolver, actor, activity, distribute) => {
 		throw new Error();
 	}
 
-	if (!distribute) {
-		const { _id } = await Reaction.findOne({
-			userId: actor._id,
-			postId: post._id
-		});
-
-		return {
-			resolver,
-			object: { $ref: 'postPeactions', $id: _id }
-		};
-	}
-
-	const promisedReaction = Reaction.insert({
-		createdAt: new Date(),
-		userId: actor._id,
-		postId: post._id,
-		reaction: 'pudding'
-	}).then(reaction => new Promise<IPostReaction>((resolve, reject) => {
-		queue.create('http', {
-			type: 'reaction',
-			reactionId: reaction._id
-		}).save(error => {
-			if (error) {
-				reject(error);
-			} else {
-				resolve(reaction);
-			}
-		});
-	}), async error => {
-		// duplicate key error
-		if (error instanceof MongoError && error.code === 11000) {
-			return Reaction.findOne({
-				userId: actor._id,
-				postId: post._id
-			});
-		}
-
-		throw error;
-	});
-
-	return promisedReaction.then(({ _id }) => ({
-		resolver,
-		object: { $ref: 'postPeactions', $id: _id }
-	}));
+	await create(actor, post, 'pudding');
 };
diff --git a/src/remote/activitypub/act/undo/follow.ts b/src/remote/activitypub/act/undo/follow.ts
new file mode 100644
index 0000000000..fcf27c9507
--- /dev/null
+++ b/src/remote/activitypub/act/undo/follow.ts
@@ -0,0 +1,26 @@
+import parseAcct from '../../../../acct/parse';
+import User, { IRemoteUser } from '../../../../models/user';
+import config from '../../../../config';
+import unfollow from '../../../../services/following/delete';
+import { IFollow } from '../../type';
+
+export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
+	const prefix = config.url + '/@';
+	const id = typeof activity == 'string' ? activity : activity.id;
+
+	if (!id.startsWith(prefix)) {
+		return null;
+	}
+
+	const { username, host } = parseAcct(id.slice(prefix.length));
+	if (host !== null) {
+		throw new Error();
+	}
+
+	const followee = await User.findOne({ username, host });
+	if (followee === null) {
+		throw new Error();
+	}
+
+	await unfollow(actor, followee, activity);
+};
diff --git a/src/remote/activitypub/act/undo/index.ts b/src/remote/activitypub/act/undo/index.ts
index aa60d3a4fa..3ede9fcfb8 100644
--- a/src/remote/activitypub/act/undo/index.ts
+++ b/src/remote/activitypub/act/undo/index.ts
@@ -1,27 +1,37 @@
-import act from '../../act';
-import deleteObject from '../../delete';
-import unfollow from './unfollow';
+import * as debug from 'debug';
+
+import { IRemoteUser } from '../../../../models/user';
+import { IUndo } from '../../type';
+import unfollow from './follow';
 import Resolver from '../../resolver';
 
-export default async (resolver: Resolver, actor, activity): Promise<void> => {
+const log = debug('misskey:activitypub');
+
+export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
 	if ('actor' in activity && actor.account.uri !== activity.actor) {
-		throw new Error();
+		throw new Error('invalid actor');
 	}
 
-	const results = await act(resolver, actor, activity.object);
+	const uri = activity.id || activity;
 
-	await Promise.all(results.map(async promisedResult => {
-		const result = await promisedResult;
+	log(`Undo: ${uri}`);
 
-		if (result === null || await deleteObject(result) !== null) {
-			return;
-		}
+	const resolver = new Resolver();
 
-		switch (result.object.$ref) {
-		case 'following':
-			await unfollow(result.object);
-		}
-	}));
+	let object;
+
+	try {
+		object = await resolver.resolve(activity.object);
+	} catch (e) {
+		log(`Resolution failed: ${e}`);
+		throw e;
+	}
+
+	switch (object.type) {
+		case 'Follow':
+			unfollow(actor, object);
+			break;
+	}
 
 	return null;
 };
diff --git a/src/remote/activitypub/act/undo/unfollow.ts b/src/remote/activitypub/act/undo/unfollow.ts
deleted file mode 100644
index 4f15d9a3e4..0000000000
--- a/src/remote/activitypub/act/undo/unfollow.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createHttp } from '../../../../queue';
-
-export default ({ $id }) => new Promise((resolve, reject) => {
-	createHttp({ type: 'unfollow', id: $id }).save(error => {
-		if (error) {
-			reject(error);
-		} else {
-			resolve();
-		}
-	});
-});
diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts
deleted file mode 100644
index 3bc0c66f35..0000000000
--- a/src/remote/activitypub/create.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { JSDOM } from 'jsdom';
-import { ObjectID } from 'mongodb';
-import config from '../../config';
-import DriveFile from '../../models/drive-file';
-import Post from '../../models/post';
-import { IRemoteUser } from '../../models/user';
-import uploadFromUrl from '../../drive/upload-from-url';
-import createPost from '../../post/create';
-import distributePost from '../../post/distribute';
-import resolvePerson from './resolve-person';
-import Resolver from './resolver';
-const createDOMPurify = require('dompurify');
-
-type IResult = {
-	resolver: Resolver;
-	object: {
-		$ref: string;
-		$id: ObjectID;
-	};
-};
-
-class Creator {
-	private actor: IRemoteUser;
-	private distribute: boolean;
-
-	constructor(actor, distribute) {
-		this.actor = actor;
-		this.distribute = distribute;
-	}
-
-	private async createImage(resolver: Resolver, image) {
-		if ('attributedTo' in image && this.actor.account.uri !== image.attributedTo) {
-			throw new Error();
-		}
-
-		const { _id } = await uploadFromUrl(image.url, this.actor, image.id || null);
-		return {
-			resolver,
-			object: { $ref: 'driveFiles.files', $id: _id }
-		};
-	}
-
-	private async createNote(resolver: Resolver, note) {
-		if (
-			('attributedTo' in note && this.actor.account.uri !== note.attributedTo) ||
-			typeof note.id !== 'string'
-		) {
-			throw new Error();
-		}
-
-		const { window } = new JSDOM(note.content);
-		const mentions = [];
-		const tags = [];
-
-		for (const { href, name, type } of note.tags) {
-			switch (type) {
-			case 'Hashtag':
-				if (name.startsWith('#')) {
-					tags.push(name.slice(1));
-				}
-				break;
-
-			case 'Mention':
-				mentions.push(resolvePerson(resolver, href));
-				break;
-			}
-		}
-
-		const [mediaIds, reply] = await Promise.all([
-			'attachment' in note && this.create(resolver, note.attachment)
-				.then(collection => Promise.all(collection))
-				.then(collection => collection
-					.filter(media => media !== null && media.object.$ref === 'driveFiles.files')
-					.map(({ object }: IResult) => object.$id)),
-
-			'inReplyTo' in note && this.create(resolver, note.inReplyTo)
-				.then(collection => Promise.all(collection.map(promise => promise.then(result => {
-					if (result !== null && result.object.$ref === 'posts') {
-						throw result.object;
-					}
-				}, () => { }))))
-				.then(() => null, ({ $id }) => Post.findOne({ _id: $id }))
-		]);
-
-		const inserted = await createPost({
-			channelId: undefined,
-			index: undefined,
-			createdAt: new Date(note.published),
-			mediaIds,
-			poll: undefined,
-			text: window.document.body.textContent,
-			textHtml: note.content && createDOMPurify(window).sanitize(note.content),
-			userId: this.actor._id,
-			appId: null,
-			viaMobile: false,
-			geo: undefined,
-			uri: note.id,
-			tags
-		}, reply, null, await Promise.all(mentions));
-
-		const promises = [];
-
-		if (this.distribute) {
-			promises.push(distributePost(this.actor, inserted.mentions, inserted));
-		}
-
-		// Register to search database
-		if (note.content && config.elasticsearch.enable) {
-			const es = require('../../db/elasticsearch');
-
-			promises.push(new Promise((resolve, reject) => {
-				es.index({
-					index: 'misskey',
-					type: 'post',
-					id: inserted._id.toString(),
-					body: {
-						text: window.document.body.textContent
-					}
-				}, resolve);
-			}));
-		}
-
-		await Promise.all(promises);
-
-		return {
-			resolver,
-			object: { $ref: 'posts', id: inserted._id }
-		};
-	}
-
-	public async create(parentResolver: Resolver, value): Promise<Array<Promise<IResult>>> {
-		const collection = await parentResolver.resolveCollection(value);
-
-		return collection.object.map(async element => {
-			const uri = element.id || element;
-
-			try {
-				await Promise.all([
-					DriveFile.findOne({ 'metadata.uri': uri }).then(file => {
-						if (file === null) {
-							return;
-						}
-
-						throw {
-							$ref: 'driveFile.files',
-							$id: file._id
-						};
-					}, () => {}),
-					Post.findOne({ uri }).then(post => {
-						if (post === null) {
-							return;
-						}
-
-						throw {
-							$ref: 'posts',
-							$id: post._id
-						};
-					}, () => {})
-				]);
-			} catch (object) {
-				return {
-					resolver: collection.resolver,
-					object
-				};
-			}
-
-			const { resolver, object } = await collection.resolver.resolveOne(element);
-
-			switch (object.type) {
-			case 'Image':
-				return this.createImage(resolver, object);
-
-			case 'Note':
-				return this.createNote(resolver, object);
-			}
-
-			return null;
-		});
-	}
-}
-
-export default (resolver: Resolver, actor, value, distribute?: boolean) => {
-	const creator = new Creator(actor, distribute);
-	return creator.create(resolver, value);
-};
diff --git a/src/remote/activitypub/delete/index.ts b/src/remote/activitypub/delete/index.ts
deleted file mode 100644
index bc9104284b..0000000000
--- a/src/remote/activitypub/delete/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import deletePost from './post';
-
-export default async ({ object }) => {
-	switch (object.$ref) {
-	case 'posts':
-		return deletePost(object);
-	}
-
-	return null;
-};
diff --git a/src/remote/activitypub/delete/post.ts b/src/remote/activitypub/delete/post.ts
deleted file mode 100644
index 59ae8c2b94..0000000000
--- a/src/remote/activitypub/delete/post.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import Post from '../../../models/post';
-import { createDb } from '../../../queue';
-
-export default async ({ $id }) => {
-	const promisedDeletion = Post.findOneAndDelete({ _id: $id });
-
-	await new Promise((resolve, reject) => createDb({
-		type: 'deletePostDependents',
-		id: $id
-	}).delay(65536).save(error => error ? reject(error) : resolve()));
-
-	return promisedDeletion;
-};
diff --git a/src/remote/activitypub/renderer/like.ts b/src/remote/activitypub/renderer/like.ts
new file mode 100644
index 0000000000..903b10789e
--- /dev/null
+++ b/src/remote/activitypub/renderer/like.ts
@@ -0,0 +1,9 @@
+import config from '../../../config';
+
+export default (user, post) => {
+	return {
+		type: 'Like',
+		actor: `${config.url}/@${user.username}`,
+		object: post.uri ? post.uri : `${config.url}/posts/${post._id}`
+	};
+};
diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts
index 43531b121a..bbab63db36 100644
--- a/src/remote/activitypub/renderer/note.ts
+++ b/src/remote/activitypub/renderer/note.ts
@@ -2,11 +2,14 @@ import renderDocument from './document';
 import renderHashtag from './hashtag';
 import config from '../../../config';
 import DriveFile from '../../../models/drive-file';
-import Post from '../../../models/post';
-import User from '../../../models/user';
+import Post, { IPost } from '../../../models/post';
+import User, { IUser } from '../../../models/user';
+
+export default async (user: IUser, post: IPost) => {
+	const promisedFiles = post.mediaIds
+		? DriveFile.find({ _id: { $in: post.mediaIds } })
+		: Promise.resolve([]);
 
-export default async (user, post) => {
-	const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } });
 	let inReplyTo;
 
 	if (post.replyId) {
@@ -16,11 +19,11 @@ export default async (user, post) => {
 
 		if (inReplyToPost !== null) {
 			const inReplyToUser = await User.findOne({
-				_id: post.userId,
+				_id: inReplyToPost.userId,
 			});
 
 			if (inReplyToUser !== null) {
-				inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`;
+				inReplyTo = inReplyToPost.uri || `${config.url}/posts/${inReplyToPost._id}`;
 			}
 		}
 	} else {
@@ -30,7 +33,7 @@ export default async (user, post) => {
 	const attributedTo = `${config.url}/@${user.username}`;
 
 	return {
-		id: `${attributedTo}/${post._id}`,
+		id: `${config.url}/posts/${post._id}`,
 		type: 'Note',
 		attributedTo,
 		content: post.textHtml,
@@ -39,6 +42,6 @@ export default async (user, post) => {
 		cc: `${attributedTo}/followers`,
 		inReplyTo,
 		attachment: (await promisedFiles).map(renderDocument),
-		tag: post.tags.map(renderHashtag)
+		tag: (post.tags || []).map(renderHashtag)
 	};
 };
diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts
index a7c0020dd8..b3bac3cd3f 100644
--- a/src/remote/activitypub/resolve-person.ts
+++ b/src/remote/activitypub/resolve-person.ts
@@ -1,42 +1,50 @@
 import { JSDOM } from 'jsdom';
 import { toUnicode } from 'punycode';
+import parseAcct from '../../acct/parse';
+import config from '../../config';
 import User, { validateUsername, isValidName, isValidDescription } from '../../models/user';
-import { createHttp } from '../../queue';
 import webFinger from '../webfinger';
-import create from './create';
+import Resolver from './resolver';
+import uploadFromUrl from '../../services/drive/upload-from-url';
+import { isCollectionOrOrderedCollection } from './type';
 
-async function isCollection(collection) {
-	return ['Collection', 'OrderedCollection'].includes(collection.type);
-}
+export default async (value, verifier?: string) => {
+	const id = value.id || value;
+	const localPrefix = config.url + '/@';
 
-export default async (parentResolver, value, verifier?: string) => {
-	const { resolver, object } = await parentResolver.resolveOne(value);
+	if (id.startsWith(localPrefix)) {
+		return User.findOne(parseAcct(id.slice(localPrefix)));
+	}
+
+	const resolver = new Resolver();
+
+	const object = await resolver.resolve(value) as any;
 
 	if (
-		object === null ||
+		object == null ||
 		object.type !== 'Person' ||
 		typeof object.preferredUsername !== 'string' ||
 		!validateUsername(object.preferredUsername) ||
-		!isValidName(object.name) ||
+		!isValidName(object.name == '' ? null : object.name) ||
 		!isValidDescription(object.summary)
 	) {
-		throw new Error();
+		throw new Error('invalid person');
 	}
 
-	const [followers, following, outbox, finger] = await Promise.all([
-		resolver.resolveOne(object.followers).then(
-			resolved => isCollection(resolved.object) ? resolved.object : null,
-			() => null
+	const [followersCount = 0, followingCount = 0, postsCount = 0, finger] = await Promise.all([
+		resolver.resolve(object.followers).then(
+			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
+			() => undefined
 		),
-		resolver.resolveOne(object.following).then(
-			resolved => isCollection(resolved.object) ? resolved.object : null,
-			() => null
+		resolver.resolve(object.following).then(
+			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
+			() => undefined
 		),
-		resolver.resolveOne(object.outbox).then(
-			resolved => isCollection(resolved.object) ? resolved.object : null,
-			() => null
+		resolver.resolve(object.outbox).then(
+			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
+			() => undefined
 		),
-		webFinger(object.id, verifier),
+		webFinger(id, verifier)
 	]);
 
 	const host = toUnicode(finger.subject.replace(/^.*?@/, ''));
@@ -47,12 +55,12 @@ export default async (parentResolver, value, verifier?: string) => {
 	const user = await User.insert({
 		avatarId: null,
 		bannerId: null,
-		createdAt: Date.parse(object.published),
+		createdAt: Date.parse(object.published) || null,
 		description: summaryDOM.textContent,
-		followersCount: followers ? followers.totalItem || 0 : 0,
-		followingCount: following ? following.totalItem || 0 : 0,
+		followersCount,
+		followingCount,
+		postsCount,
 		name: object.name,
-		postsCount: outbox ? outbox.totalItem || 0 : 0,
 		driveCapacity: 1024 * 1024 * 8, // 8MiB
 		username: object.preferredUsername,
 		usernameLower: object.preferredUsername.toLowerCase(),
@@ -64,38 +72,18 @@ export default async (parentResolver, value, verifier?: string) => {
 				publicKeyPem: object.publicKey.publicKeyPem
 			},
 			inbox: object.inbox,
-			uri: object.id,
+			uri: id,
 		},
 	});
 
-	createHttp({
-		type: 'performActivityPub',
-		actor: user._id,
-		outbox
-	}).save();
-
-	const [avatarId, bannerId] = await Promise.all([
+	const [avatarId, bannerId] = (await Promise.all([
 		object.icon,
 		object.image
-	].map(async value => {
-		if (value === undefined) {
-			return null;
-		}
-
-		try {
-			const created = await create(resolver, user, value);
-
-			await Promise.all(created.map(asyncCreated => asyncCreated.then(created => {
-				if (created !== null && created.object.$ref === 'driveFiles.files') {
-					throw created.object.$id;
-				}
-			}, () => {})));
-
-			return null;
-		} catch (id) {
-			return id;
-		}
-	}));
+	].map(img =>
+		img == null
+			? Promise.resolve(null)
+			: uploadFromUrl(img.url, user)
+	))).map(file => file != null ? file._id : null);
 
 	User.update({ _id: user._id }, { $set: { avatarId, bannerId } });
 
diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts
index 371ccdcc30..4a97e2ef66 100644
--- a/src/remote/activitypub/resolver.ts
+++ b/src/remote/activitypub/resolver.ts
@@ -1,20 +1,51 @@
-const request = require('request-promise-native');
+import * as request from 'request-promise-native';
+import * as debug from 'debug';
+import { IObject } from './type';
+
+const log = debug('misskey:activitypub:resolver');
 
 export default class Resolver {
-	private requesting: Set<string>;
+	private history: Set<string>;
 
-	constructor(iterable?: Iterable<string>) {
-		this.requesting = new Set(iterable);
+	constructor() {
+		this.history = new Set();
 	}
 
-	private async resolveUnrequestedOne(value) {
-		if (typeof value !== 'string') {
-			return { resolver: this, object: value };
+	public async resolveCollection(value) {
+		const collection = typeof value === 'string'
+			? await this.resolve(value)
+			: value;
+
+		switch (collection.type) {
+		case 'Collection':
+			collection.objects = collection.object.items;
+			break;
+
+		case 'OrderedCollection':
+			collection.objects = collection.object.orderedItems;
+			break;
+
+		default:
+			throw new Error(`unknown collection type: ${collection.type}`);
 		}
 
-		const resolver = new Resolver(this.requesting);
+		return collection;
+	}
 
-		resolver.requesting.add(value);
+	public async resolve(value): Promise<IObject> {
+		if (value == null) {
+			throw new Error('resolvee is null (or undefined)');
+		}
+
+		if (typeof value !== 'string') {
+			return value;
+		}
+
+		if (this.history.has(value)) {
+			throw new Error('cannot resolve already resolved one');
+		}
+
+		this.history.add(value);
 
 		const object = await request({
 			url: value,
@@ -29,41 +60,11 @@ export default class Resolver {
 				!object['@context'].includes('https://www.w3.org/ns/activitystreams') :
 				object['@context'] !== 'https://www.w3.org/ns/activitystreams'
 		)) {
-			throw new Error();
+			throw new Error('invalid response');
 		}
 
-		return { resolver, object };
-	}
+		log(`resolved: ${JSON.stringify(object, null, 2)}`);
 
-	public async resolveCollection(value) {
-		const resolved = typeof value === 'string' ?
-			await this.resolveUnrequestedOne(value) :
-			{ resolver: this, object: value };
-
-		switch (resolved.object.type) {
-		case 'Collection':
-			resolved.object = resolved.object.items;
-			break;
-
-		case 'OrderedCollection':
-			resolved.object = resolved.object.orderedItems;
-			break;
-
-		default:
-			if (!Array.isArray(value)) {
-				resolved.object = [resolved.object];
-			}
-			break;
-		}
-
-		return resolved;
-	}
-
-	public resolveOne(value) {
-		if (this.requesting.has(value)) {
-			throw new Error();
-		}
-
-		return this.resolveUnrequestedOne(value);
+		return object;
 	}
 }
diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts
index 94e2c350a2..450d5906d8 100644
--- a/src/remote/activitypub/type.ts
+++ b/src/remote/activitypub/type.ts
@@ -1,3 +1,70 @@
-export type IObject = {
+export type obj = { [x: string]: any };
+
+export interface IObject {
+	'@context': string | obj | obj[];
 	type: string;
-};
+	id?: string;
+	summary?: string;
+}
+
+export interface IActivity extends IObject {
+	//type: 'Activity';
+	actor: IObject | string;
+	object: IObject | string;
+	target?: IObject | string;
+}
+
+export interface ICollection extends IObject {
+	type: 'Collection';
+	totalItems: number;
+	items: IObject | string | IObject[] | string[];
+}
+
+export interface IOrderedCollection extends IObject {
+	type: 'OrderedCollection';
+	totalItems: number;
+	orderedItems: IObject | string | IObject[] | string[];
+}
+
+export const isCollection = (object: IObject): object is ICollection =>
+	object.type === 'Collection';
+
+export const isOrderedCollection = (object: IObject): object is IOrderedCollection =>
+	object.type === 'OrderedCollection';
+
+export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
+	isCollection(object) || isOrderedCollection(object);
+
+export interface ICreate extends IActivity {
+	type: 'Create';
+}
+
+export interface IDelete extends IActivity {
+	type: 'Delete';
+}
+
+export interface IUndo extends IActivity {
+	type: 'Undo';
+}
+
+export interface IFollow extends IActivity {
+	type: 'Follow';
+}
+
+export interface IAccept extends IActivity {
+	type: 'Accept';
+}
+
+export interface ILike extends IActivity {
+	type: 'Like';
+}
+
+export type Object =
+	ICollection |
+	IOrderedCollection |
+	ICreate |
+	IDelete |
+	IUndo |
+	IFollow |
+	IAccept |
+	ILike;
diff --git a/src/remote/request.ts b/src/remote/request.ts
index 72262cbf61..a375aebfbb 100644
--- a/src/remote/request.ts
+++ b/src/remote/request.ts
@@ -1,9 +1,15 @@
 import { request } from 'https';
 import { sign } from 'http-signature';
 import { URL } from 'url';
+import * as debug from 'debug';
+
 import config from '../config';
 
+const log = debug('misskey:activitypub:deliver');
+
 export default ({ account, username }, url, object) => new Promise((resolve, reject) => {
+	log(`--> ${url}`);
+
 	const { protocol, hostname, port, pathname, search } = new URL(url);
 
 	const req = request({
@@ -14,6 +20,8 @@ export default ({ account, username }, url, object) => new Promise((resolve, rej
 		path: pathname + search,
 	}, res => {
 		res.on('end', () => {
+			log(`${url} --> ${res.statusCode}`);
+
 			if (res.statusCode >= 200 && res.statusCode < 300) {
 				resolve();
 			} else {
diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts
index 097ed66738..9e1ae51952 100644
--- a/src/remote/resolve-user.ts
+++ b/src/remote/resolve-user.ts
@@ -1,7 +1,6 @@
 import { toUnicode, toASCII } from 'punycode';
 import User from '../models/user';
 import resolvePerson from './activitypub/resolve-person';
-import Resolver from './activitypub/resolver';
 import webFinger from './webfinger';
 
 export default async (username, host, option) => {
@@ -17,10 +16,10 @@ export default async (username, host, option) => {
 		const finger = await webFinger(acctLower, acctLower);
 		const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
 		if (!self) {
-			throw new Error();
+			throw new Error('self link not found');
 		}
 
-		user = await resolvePerson(new Resolver(), self.href, acctLower);
+		user = await resolvePerson(self.href, acctLower);
 	}
 
 	return user;
diff --git a/src/renderers/get-notification-summary.ts b/src/renderers/get-notification-summary.ts
index 03db722c84..f5e38faf99 100644
--- a/src/renderers/get-notification-summary.ts
+++ b/src/renderers/get-notification-summary.ts
@@ -1,3 +1,4 @@
+import getUserName from '../renderers/get-user-name';
 import getPostSummary from './get-post-summary';
 import getReactionEmoji from './get-reaction-emoji';
 
@@ -8,19 +9,19 @@ import getReactionEmoji from './get-reaction-emoji';
 export default function(notification: any): string {
 	switch (notification.type) {
 		case 'follow':
-			return `${notification.user.name}にフォローされました`;
+			return `${getUserName(notification.user)}にフォローされました`;
 		case 'mention':
-			return `言及されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`;
+			return `言及されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`;
 		case 'reply':
-			return `返信されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`;
+			return `返信されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`;
 		case 'repost':
-			return `Repostされました:\n${notification.user.name}「${getPostSummary(notification.post)}」`;
+			return `Repostされました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`;
 		case 'quote':
-			return `引用されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`;
+			return `引用されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`;
 		case 'reaction':
-			return `リアクションされました:\n${notification.user.name} <${getReactionEmoji(notification.reaction)}>「${getPostSummary(notification.post)}」`;
+			return `リアクションされました:\n${getUserName(notification.user)} <${getReactionEmoji(notification.reaction)}>「${getPostSummary(notification.post)}」`;
 		case 'poll_vote':
-			return `投票されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`;
+			return `投票されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`;
 		default:
 			return `<不明な通知タイプ: ${notification.type}>`;
 	}
diff --git a/src/renderers/get-user-name.ts b/src/renderers/get-user-name.ts
new file mode 100644
index 0000000000..acd5e6626d
--- /dev/null
+++ b/src/renderers/get-user-name.ts
@@ -0,0 +1,5 @@
+import { IUser } from '../models/user';
+
+export default function(user: IUser): string {
+	return user.name || '名無し';
+}
diff --git a/src/renderers/get-user-summary.ts b/src/renderers/get-user-summary.ts
index 2089871130..d002933b6d 100644
--- a/src/renderers/get-user-summary.ts
+++ b/src/renderers/get-user-summary.ts
@@ -1,12 +1,13 @@
 import { IUser, isLocalUser } from '../models/user';
 import getAcct from '../acct/render';
+import getUserName from './get-user-name';
 
 /**
  * ユーザーを表す文字列を取得します。
  * @param user ユーザー
  */
 export default function(user: IUser): string {
-	let string = `${user.name} (@${getAcct(user)})\n` +
+	let string = `${getUserName(user)} (@${getAcct(user)})\n` +
 		`${user.postsCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`;
 
 	if (isLocalUser(user)) {
diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts
index 0907823b23..1b6cc0c00a 100644
--- a/src/server/activitypub/inbox.ts
+++ b/src/server/activitypub/inbox.ts
@@ -3,9 +3,7 @@ import * as express from 'express';
 import { parseRequest } from 'http-signature';
 import { createHttp } from '../../queue';
 
-const app = express();
-
-app.disable('x-powered-by');
+const app = express.Router();
 
 app.post('/@:user/inbox', bodyParser.json({
 	type() {
@@ -24,7 +22,7 @@ app.post('/@:user/inbox', bodyParser.json({
 
 	createHttp({
 		type: 'processInbox',
-		inbox: req.body,
+		activity: req.body,
 		signature,
 	}).save();
 
diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts
index 9ecb0c0711..976908d1f3 100644
--- a/src/server/activitypub/outbox.ts
+++ b/src/server/activitypub/outbox.ts
@@ -6,8 +6,7 @@ import config from '../../config';
 import Post from '../../models/post';
 import withUser from './with-user';
 
-const app = express();
-app.disable('x-powered-by');
+const app = express.Router();
 
 app.get('/@:user/outbox', withUser(username => {
 	return `${config.url}/@${username}/inbox`;
diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts
index 91d91aeb95..355c603563 100644
--- a/src/server/activitypub/post.ts
+++ b/src/server/activitypub/post.ts
@@ -5,8 +5,7 @@ import parseAcct from '../../acct/parse';
 import Post from '../../models/post';
 import User from '../../models/user';
 
-const app = express();
-app.disable('x-powered-by');
+const app = express.Router();
 
 app.get('/@:user/:post', async (req, res, next) => {
 	const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']);
diff --git a/src/server/activitypub/publickey.ts b/src/server/activitypub/publickey.ts
index c564c437e6..b48504927a 100644
--- a/src/server/activitypub/publickey.ts
+++ b/src/server/activitypub/publickey.ts
@@ -4,8 +4,7 @@ import render from '../../remote/activitypub/renderer/key';
 import config from '../../config';
 import withUser from './with-user';
 
-const app = express();
-app.disable('x-powered-by');
+const app = express.Router();
 
 app.get('/@:user/publickey', withUser(username => {
 	return `${config.url}/@${username}/publickey`;
diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts
index baf2dc9a05..f054974510 100644
--- a/src/server/activitypub/user.ts
+++ b/src/server/activitypub/user.ts
@@ -11,8 +11,7 @@ const respond = withUser(username => `${config.url}/@${username}`, (user, req, r
 	res.json(rendered);
 });
 
-const app = express();
-app.disable('x-powered-by');
+const app = express.Router();
 
 app.get('/@:user', (req, res, next) => {
 	const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']);
diff --git a/src/server/api/bot/core.ts b/src/server/api/bot/core.ts
index 7e80f31e5e..a44aa9d7bc 100644
--- a/src/server/api/bot/core.ts
+++ b/src/server/api/bot/core.ts
@@ -4,6 +4,7 @@ import * as bcrypt from 'bcryptjs';
 import User, { IUser, init as initUser, ILocalUser } from '../../../models/user';
 
 import getPostSummary from '../../../renderers/get-post-summary';
+import getUserName from '../../../renderers/get-user-name';
 import getUserSummary from '../../../renderers/get-user-summary';
 import parseAcct from '../../../acct/parse';
 import getNotificationSummary from '../../../renderers/get-notification-summary';
@@ -90,7 +91,7 @@ export default class BotCore extends EventEmitter {
 					'タイムラインや通知を見た後、「次」というとさらに遡ることができます。';
 
 			case 'me':
-				return this.user ? `${this.user.name}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません';
+				return this.user ? `${getUserName(this.user)}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません';
 
 			case 'login':
 			case 'signin':
@@ -230,7 +231,7 @@ class SigninContext extends Context {
 			if (same) {
 				this.bot.signin(this.temporaryUser);
 				this.bot.clearContext();
-				return `${this.temporaryUser.name}さん、おかえりなさい!`;
+				return `${getUserName(this.temporaryUser)}さん、おかえりなさい!`;
 			} else {
 				return `パスワードが違います... もう一度教えてください:`;
 			}
@@ -305,7 +306,7 @@ class TlContext extends Context {
 			this.emit('updated');
 
 			const text = tl
-				.map(post => `${post.user.name}\n「${getPostSummary(post)}」`)
+				.map(post => `${getUserName(post.user)}\n「${getPostSummary(post)}」`)
 				.join('\n-----\n');
 
 			return text;
diff --git a/src/server/api/bot/interfaces/line.ts b/src/server/api/bot/interfaces/line.ts
index 7847cbdeaa..1191aaf391 100644
--- a/src/server/api/bot/interfaces/line.ts
+++ b/src/server/api/bot/interfaces/line.ts
@@ -10,6 +10,7 @@ import prominence = require('prominence');
 import getAcct from '../../../../acct/render';
 import parseAcct from '../../../../acct/parse';
 import getPostSummary from '../../../../renderers/get-post-summary';
+import getUserName from '../../../../renderers/get-user-name';
 
 const redis = prominence(_redis);
 
@@ -131,7 +132,7 @@ class LineBot extends BotCore {
 			template: {
 				type: 'buttons',
 				thumbnailImageUrl: `${user.avatarUrl}?thumbnail&size=1024`,
-				title: `${user.name} (@${acct})`,
+				title: `${getUserName(user)} (@${acct})`,
 				text: user.description || '(no description)',
 				actions: actions
 			}
@@ -146,7 +147,7 @@ class LineBot extends BotCore {
 			limit: 5
 		}, this.user);
 
-		const text = `${tl[0].user.name}さんのタイムラインはこちらです:\n\n` + tl
+		const text = `${getUserName(tl[0].user)}さんのタイムラインはこちらです:\n\n` + tl
 			.map(post => getPostSummary(post))
 			.join('\n-----\n');
 
diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts
index 9ccbe20171..0ccac8d83d 100644
--- a/src/server/api/endpoints/following/create.ts
+++ b/src/server/api/endpoints/following/create.ts
@@ -4,7 +4,7 @@
 import $ from 'cafy';
 import User from '../../../../models/user';
 import Following from '../../../../models/following';
-import { createHttp } from '../../../../queue';
+import create from '../../../../services/following/create';
 
 /**
  * Follow a user
@@ -50,15 +50,8 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
 	}
 
 	// Create following
-	const { _id } = await Following.insert({
-		createdAt: new Date(),
-		followerId: follower._id,
-		followeeId: followee._id
-	});
-
-	createHttp({ type: 'follow', following: _id }).save();
+	create(follower, followee);
 
 	// Send response
 	res();
-
 });
diff --git a/src/server/api/endpoints/posts/create.ts b/src/server/api/endpoints/posts/create.ts
index 47897626f1..003a892bc0 100644
--- a/src/server/api/endpoints/posts/create.ts
+++ b/src/server/api/endpoints/posts/create.ts
@@ -3,17 +3,12 @@
  */
 import $ from 'cafy';
 import deepEqual = require('deep-equal');
-import parseAcct from '../../../../acct/parse';
-import renderAcct from '../../../../acct/render';
-import config from '../../../../config';
-import html from '../../../../text/html';
-import parse from '../../../../text/parse';
-import Post, { IPost, isValidText, isValidCw } from '../../../../models/post';
-import User, { ILocalUser } from '../../../../models/user';
+import Post, { IPost, isValidText, isValidCw, pack } from '../../../../models/post';
+import { ILocalUser } from '../../../../models/user';
 import Channel, { IChannel } from '../../../../models/channel';
 import DriveFile from '../../../../models/drive-file';
-import create from '../../../../post/create';
-import distribute from '../../../../post/distribute';
+import create from '../../../../services/post/create';
+import { IApp } from '../../../../models/app';
 
 /**
  * Create a post
@@ -23,7 +18,7 @@ import distribute from '../../../../post/distribute';
  * @param {any} app
  * @return {Promise<any>}
  */
-module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) => {
+module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
 	// Get 'visibility' parameter
 	const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'unlisted', 'private', 'direct']).$;
 	if (visibilityErr) return rej('invalid visibility');
@@ -231,85 +226,26 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej)
 		}
 	}
 
-	let tokens = null;
-	if (text) {
-		// Analyze
-		tokens = parse(text);
-
-		// Extract hashtags
-		const hashtags = tokens
-			.filter(t => t.type == 'hashtag')
-			.map(t => t.hashtag);
-
-		hashtags.forEach(tag => {
-			if (tags.indexOf(tag) == -1) {
-				tags.push(tag);
-			}
-		});
-	}
-
-	let atMentions = [];
-
-	// If has text content
-	if (text) {
-		/*
-				// Extract a hashtags
-				const hashtags = tokens
-					.filter(t => t.type == 'hashtag')
-					.map(t => t.hashtag)
-					// Drop dupulicates
-					.filter((v, i, s) => s.indexOf(v) == i);
-
-				// ハッシュタグをデータベースに登録
-				registerHashtags(user, hashtags);
-		*/
-		// Extract an '@' mentions
-		atMentions = tokens
-			.filter(t => t.type == 'mention')
-			.map(renderAcct)
-			// Drop dupulicates
-			.filter((v, i, s) => s.indexOf(v) == i)
-			// Fetch mentioned user
-			// SELECT _id
-			.map(mention => User.findOne(parseAcct(mention), { _id: true }));
-	}
-
 	// 投稿を作成
-	const post = await create({
+	const post = await create(user, {
 		createdAt: new Date(),
-		channelId: channel ? channel._id : undefined,
-		index: channel ? channel.index + 1 : undefined,
-		mediaIds: files ? files.map(file => file._id) : [],
+		media: files,
 		poll: poll,
 		text: text,
-		textHtml: tokens === null ? null : html(tokens),
+		reply,
+		repost,
 		cw: cw,
 		tags: tags,
-		userId: user._id,
-		appId: app ? app._id : null,
+		app: app,
 		viaMobile: viaMobile,
 		visibility,
 		geo
-	}, reply, repost, await Promise.all(atMentions));
+	});
 
-	const postObj = await distribute(user, post.mentions, post);
+	const postObj = await pack(post, user);
 
 	// Reponse
 	res({
 		createdPost: postObj
 	});
-
-	// Register to search database
-	if (post.text && config.elasticsearch.enable) {
-		const es = require('../../../db/elasticsearch');
-
-		es.index({
-			index: 'misskey',
-			type: 'post',
-			id: post._id.toString(),
-			body: {
-				text: post.text
-			}
-		});
-	}
 });
diff --git a/src/server/api/endpoints/posts/reactions/create.ts b/src/server/api/endpoints/posts/reactions/create.ts
index f1b0c7dd29..71fa6a2955 100644
--- a/src/server/api/endpoints/posts/reactions/create.ts
+++ b/src/server/api/endpoints/posts/reactions/create.ts
@@ -3,20 +3,11 @@
  */
 import $ from 'cafy';
 import Reaction from '../../../../../models/post-reaction';
-import Post, { pack as packPost } from '../../../../../models/post';
-import { pack as packUser } from '../../../../../models/user';
-import Watching from '../../../../../models/post-watching';
-import watch from '../../../../../post/watch';
-import { publishPostStream } from '../../../../../publishers/stream';
-import notify from '../../../../../publishers/notify';
-import pushSw from '../../../../../publishers/push-sw';
+import Post from '../../../../../models/post';
+import create from '../../../../../services/post/reaction/create';
 
 /**
  * React to a post
- *
- * @param {any} params
- * @param {any} user
- * @return {Promise<any>}
  */
 module.exports = (params, user) => new Promise(async (res, rej) => {
 	// Get 'postId' parameter
@@ -46,78 +37,11 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
 		return rej('post not found');
 	}
 
-	// Myself
-	if (post.userId.equals(user._id)) {
-		return rej('cannot react to my post');
+	try {
+		await create(user, post, reaction);
+	} catch (e) {
+		rej(e);
 	}
 
-	// if already reacted
-	const exist = await Reaction.findOne({
-		postId: post._id,
-		userId: user._id,
-		deletedAt: { $exists: false }
-	});
-
-	if (exist !== null) {
-		return rej('already reacted');
-	}
-
-	// Create reaction
-	await Reaction.insert({
-		createdAt: new Date(),
-		postId: post._id,
-		userId: user._id,
-		reaction: reaction
-	});
-
-	// Send response
 	res();
-
-	const inc = {};
-	inc[`reactionCounts.${reaction}`] = 1;
-
-	// Increment reactions count
-	await Post.update({ _id: post._id }, {
-		$inc: inc
-	});
-
-	publishPostStream(post._id, 'reacted');
-
-	// Notify
-	notify(post.userId, user._id, 'reaction', {
-		postId: post._id,
-		reaction: reaction
-	});
-
-	pushSw(post.userId, 'reaction', {
-		user: await packUser(user, post.userId),
-		post: await packPost(post, post.userId),
-		reaction: reaction
-	});
-
-	// Fetch watchers
-	Watching
-		.find({
-			postId: post._id,
-			userId: { $ne: user._id },
-			// 削除されたドキュメントは除く
-			deletedAt: { $exists: false }
-		}, {
-			fields: {
-				userId: true
-			}
-		})
-		.then(watchers => {
-			watchers.forEach(watcher => {
-				notify(watcher.userId, user._id, 'reaction', {
-					postId: post._id,
-					reaction: reaction
-				});
-			});
-		});
-
-	// この投稿をWatchする
-	if (user.account.settings.autoWatch !== false) {
-		watch(user._id, post);
-	}
 });
diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts
index 2b02799378..d272ce4639 100644
--- a/src/server/api/endpoints/users/show.ts
+++ b/src/server/api/endpoints/users/show.ts
@@ -37,7 +37,8 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
 	if (typeof host === 'string') {
 		try {
 			user = await resolveRemoteUser(username, host, cursorOption);
-		} catch (exception) {
+		} catch (e) {
+			console.warn(`failed to resolve remote user: ${e}`);
 			return rej('failed to resolve remote user');
 		}
 	} else {
diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts
index 4203ce526d..c54d6f1a1b 100644
--- a/src/server/api/private/signup.ts
+++ b/src/server/api/private/signup.ts
@@ -47,7 +47,6 @@ export default async (req: express.Request, res: express.Response) => {
 
 	const username = req.body['username'];
 	const password = req.body['password'];
-	const name = '名無し';
 
 	// Validate username
 	if (!validateUsername(username)) {
@@ -113,7 +112,7 @@ export default async (req: express.Request, res: express.Response) => {
 		description: null,
 		followersCount: 0,
 		followingCount: 0,
-		name: name,
+		name: null,
 		postsCount: 0,
 		driveCapacity: 1024 * 1024 * 128, // 128MiB
 		username: username,
diff --git a/src/server/web/index.ts b/src/server/web/index.ts
index 1445d1aefa..5b1b6409b9 100644
--- a/src/server/web/index.ts
+++ b/src/server/web/index.ts
@@ -11,7 +11,7 @@ import * as bodyParser from 'body-parser';
 import * as favicon from 'serve-favicon';
 import * as compression from 'compression';
 
-const client = `${__dirname}/../../client/`;
+const client = path.resolve(`${__dirname}/../../client/`);
 
 // Create server
 const app = express();
diff --git a/src/server/webfinger.ts b/src/server/webfinger.ts
index 20057da31f..fd7ebc3fb5 100644
--- a/src/server/webfinger.ts
+++ b/src/server/webfinger.ts
@@ -1,11 +1,12 @@
+import * as express from 'express';
+
 import config from '../config';
 import parseAcct from '../acct/parse';
 import User from '../models/user';
-const express = require('express');
 
 const app = express();
 
-app.get('/.well-known/webfinger', async (req, res) => {
+app.get('/.well-known/webfinger', async (req: express.Request, res: express.Response) => {
 	if (typeof req.query.resource !== 'string') {
 		return res.sendStatus(400);
 	}
@@ -34,13 +35,15 @@ app.get('/.well-known/webfinger', async (req, res) => {
 
 	return res.json({
 		subject: `acct:${user.username}@${config.host}`,
-		links: [
-			{
-				rel: 'self',
-				type: 'application/activity+json',
-				href: `${config.url}/@${user.username}`
-			}
-		]
+		links: [{
+			rel: 'self',
+			type: 'application/activity+json',
+			href: `${config.url}/@${user.username}`
+		}, {
+			rel: 'http://webfinger.net/rel/profile-page',
+			type: 'text/html',
+			href: `${config.url}/@${user.username}`
+		}]
 	});
 });
 
diff --git a/src/drive/add-file.ts b/src/services/drive/add-file.ts
similarity index 95%
rename from src/drive/add-file.ts
rename to src/services/drive/add-file.ts
index 24eb5208d5..64a2f18340 100644
--- a/src/drive/add-file.ts
+++ b/src/services/drive/add-file.ts
@@ -10,12 +10,12 @@ import * as debug from 'debug';
 import fileType = require('file-type');
 import prominence = require('prominence');
 
-import DriveFile, { IMetadata, getGridFSBucket } from '../models/drive-file';
-import DriveFolder from '../models/drive-folder';
-import { pack } from '../models/drive-file';
-import event, { publishDriveStream } from '../publishers/stream';
-import getAcct from '../acct/render';
-import config from '../config';
+import DriveFile, { IMetadata, getGridFSBucket } from '../../models/drive-file';
+import DriveFolder from '../../models/drive-folder';
+import { pack } from '../../models/drive-file';
+import event, { publishDriveStream } from '../../publishers/stream';
+import getAcct from '../../acct/render';
+import config from '../../config';
 
 const gm = _gm.subClass({
 	imageMagick: true
diff --git a/src/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts
similarity index 82%
rename from src/drive/upload-from-url.ts
rename to src/services/drive/upload-from-url.ts
index f96af0f266..676586cd15 100644
--- a/src/drive/upload-from-url.ts
+++ b/src/services/drive/upload-from-url.ts
@@ -1,19 +1,23 @@
 import * as URL from 'url';
-import { IDriveFile, validateFileName } from '../models/drive-file';
+import { IDriveFile, validateFileName } from '../../models/drive-file';
 import create from './add-file';
 import * as debug from 'debug';
 import * as tmp from 'tmp';
 import * as fs from 'fs';
 import * as request from 'request';
 
-const log = debug('misskey:common:drive:upload_from_url');
+const log = debug('misskey:drive:upload-from-url');
 
 export default async (url, user, folderId = null, uri = null): Promise<IDriveFile> => {
+	log(`REQUESTED: ${url}`);
+
 	let name = URL.parse(url).pathname.split('/').pop();
 	if (!validateFileName(name)) {
 		name = null;
 	}
 
+	log(`name: ${name}`);
+
 	// Create temp file
 	const path = await new Promise((res: (string) => void, rej) => {
 		tmp.file((e, path) => {
@@ -37,6 +41,8 @@ export default async (url, user, folderId = null, uri = null): Promise<IDriveFil
 
 	const driveFile = await create(user, path, name, null, folderId, false, uri);
 
+	log(`created: ${driveFile._id}`);
+
 	// clean-up
 	fs.unlink(path, (e) => {
 		if (e) log(e.stack);
diff --git a/src/services/following/create.ts b/src/services/following/create.ts
new file mode 100644
index 0000000000..d919f4487f
--- /dev/null
+++ b/src/services/following/create.ts
@@ -0,0 +1,72 @@
+import User, { isLocalUser, isRemoteUser, pack as packUser, IUser } from '../../models/user';
+import Following from '../../models/following';
+import FollowingLog from '../../models/following-log';
+import FollowedLog from '../../models/followed-log';
+import event from '../../publishers/stream';
+import notify from '../../publishers/notify';
+import context from '../../remote/activitypub/renderer/context';
+import renderFollow from '../../remote/activitypub/renderer/follow';
+import renderAccept from '../../remote/activitypub/renderer/accept';
+import { deliver } from '../../queue';
+
+export default async function(follower: IUser, followee: IUser, activity?) {
+	const following = await Following.insert({
+		createdAt: new Date(),
+		followerId: follower._id,
+		followeeId: followee._id
+	});
+
+	//#region Increment following count
+	User.update({ _id: follower._id }, {
+		$inc: {
+			followingCount: 1
+		}
+	});
+
+	FollowingLog.insert({
+		createdAt: following.createdAt,
+		userId: follower._id,
+		count: follower.followingCount + 1
+	});
+	//#endregion
+
+	//#region Increment followers count
+	User.update({ _id: followee._id }, {
+		$inc: {
+			followersCount: 1
+		}
+	});
+	FollowedLog.insert({
+		createdAt: following.createdAt,
+		userId: followee._id,
+		count: followee.followersCount + 1
+	});
+	//#endregion
+
+	// Publish follow event
+	if (isLocalUser(follower)) {
+		packUser(followee, follower).then(packed => event(follower._id, 'follow', packed));
+	}
+
+	// Publish followed event
+	if (isLocalUser(followee)) {
+		packUser(follower, followee).then(packed => event(followee._id, 'followed', packed)),
+
+		// 通知を作成
+		notify(followee._id, follower._id, 'follow');
+	}
+
+	if (isLocalUser(follower) && isRemoteUser(followee)) {
+		const content = renderFollow(follower, followee);
+		content['@context'] = context;
+
+		deliver(follower, content, followee.account.inbox).save();
+	}
+
+	if (isRemoteUser(follower) && isLocalUser(followee)) {
+		const content = renderAccept(activity);
+		content['@context'] = context;
+
+		deliver(followee, content, follower.account.inbox).save();
+	}
+}
diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts
new file mode 100644
index 0000000000..364a4803b9
--- /dev/null
+++ b/src/services/following/delete.ts
@@ -0,0 +1,64 @@
+import User, { isLocalUser, isRemoteUser, pack as packUser, IUser } from '../../models/user';
+import Following from '../../models/following';
+import FollowingLog from '../../models/following-log';
+import FollowedLog from '../../models/followed-log';
+import event from '../../publishers/stream';
+import context from '../../remote/activitypub/renderer/context';
+import renderFollow from '../../remote/activitypub/renderer/follow';
+import renderUndo from '../../remote/activitypub/renderer/undo';
+import { deliver } from '../../queue';
+
+export default async function(follower: IUser, followee: IUser, activity?) {
+	const following = await Following.findOne({
+		followerId: follower._id,
+		followeeId: followee._id
+	});
+
+	if (following == null) {
+		console.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
+		return;
+	}
+
+	Following.remove({
+		_id: following._id
+	});
+
+	//#region Decrement following count
+	User.update({ _id: follower._id }, {
+		$inc: {
+			followingCount: -1
+		}
+	});
+
+	FollowingLog.insert({
+		createdAt: following.createdAt,
+		userId: follower._id,
+		count: follower.followingCount - 1
+	});
+	//#endregion
+
+	//#region Decrement followers count
+	User.update({ _id: followee._id }, {
+		$inc: {
+			followersCount: -1
+		}
+	});
+	FollowedLog.insert({
+		createdAt: following.createdAt,
+		userId: followee._id,
+		count: followee.followersCount - 1
+	});
+	//#endregion
+
+	// Publish unfollow event
+	if (isLocalUser(follower)) {
+		packUser(followee, follower).then(packed => event(follower._id, 'unfollow', packed));
+	}
+
+	if (isLocalUser(follower) && isRemoteUser(followee)) {
+		const content = renderUndo(renderFollow(follower, followee));
+		content['@context'] = context;
+
+		deliver(follower, content, followee.account.inbox).save();
+	}
+}
diff --git a/src/services/post/create.ts b/src/services/post/create.ts
new file mode 100644
index 0000000000..745683b518
--- /dev/null
+++ b/src/services/post/create.ts
@@ -0,0 +1,358 @@
+import Post, { pack, IPost } from '../../models/post';
+import User, { isLocalUser, IUser, isRemoteUser } from '../../models/user';
+import stream from '../../publishers/stream';
+import Following from '../../models/following';
+import { deliver } from '../../queue';
+import renderNote from '../../remote/activitypub/renderer/note';
+import renderCreate from '../../remote/activitypub/renderer/create';
+import context from '../../remote/activitypub/renderer/context';
+import { IDriveFile } from '../../models/drive-file';
+import notify from '../../publishers/notify';
+import PostWatching from '../../models/post-watching';
+import watch from './watch';
+import Mute from '../../models/mute';
+import pushSw from '../../publishers/push-sw';
+import event from '../../publishers/stream';
+import parse from '../../text/parse';
+import html from '../../text/html';
+import { IApp } from '../../models/app';
+
+export default async (user: IUser, data: {
+	createdAt?: Date;
+	text?: string;
+	reply?: IPost;
+	repost?: IPost;
+	media?: IDriveFile[];
+	geo?: any;
+	poll?: any;
+	viaMobile?: boolean;
+	tags?: string[];
+	cw?: string;
+	visibility?: string;
+	uri?: string;
+	app?: IApp;
+}, silent = false) => new Promise<IPost>(async (res, rej) => {
+	if (data.createdAt == null) data.createdAt = new Date();
+	if (data.visibility == null) data.visibility = 'public';
+
+	const tags = data.tags || [];
+
+	let tokens = null;
+
+	if (data.text) {
+		// Analyze
+		tokens = parse(data.text);
+
+		// Extract hashtags
+		const hashtags = tokens
+			.filter(t => t.type == 'hashtag')
+			.map(t => t.hashtag);
+
+		hashtags.forEach(tag => {
+			if (tags.indexOf(tag) == -1) {
+				tags.push(tag);
+			}
+		});
+	}
+
+	const insert: any = {
+		createdAt: data.createdAt,
+		mediaIds: data.media ? data.media.map(file => file._id) : [],
+		replyId: data.reply ? data.reply._id : null,
+		repostId: data.repost ? data.repost._id : null,
+		text: data.text,
+		textHtml: tokens === null ? null : html(tokens),
+		poll: data.poll,
+		cw: data.cw,
+		tags,
+		userId: user._id,
+		viaMobile: data.viaMobile,
+		geo: data.geo || null,
+		appId: data.app ? data.app._id : null,
+		visibility: data.visibility,
+
+		// 以下非正規化データ
+		_reply: data.reply ? { userId: data.reply.userId } : null,
+		_repost: data.repost ? { userId: data.repost.userId } : null,
+		_user: {
+			host: user.host,
+			hostLower: user.hostLower,
+			account: isLocalUser(user) ? {} : {
+				inbox: user.account.inbox
+			}
+		}
+	};
+
+	if (data.uri != null) insert.uri = data.uri;
+
+	// 投稿を作成
+	const post = await Post.insert(insert);
+
+	res(post);
+
+	User.update({ _id: user._id }, {
+		// Increment posts count
+		$inc: {
+			postsCount: 1
+		},
+		// Update latest post
+		$set: {
+			latestPost: post
+		}
+	});
+
+	// Serialize
+	const postObj = await pack(post);
+
+	// タイムラインへの投稿
+	if (post.channelId == null) {
+		// Publish event to myself's stream
+		if (isLocalUser(user)) {
+			stream(post.userId, 'post', postObj);
+		}
+
+		// Fetch all followers
+		const followers = await Following.aggregate([{
+			$lookup: {
+				from: 'users',
+				localField: 'followerId',
+				foreignField: '_id',
+				as: 'user'
+			}
+		}, {
+			$match: {
+				followeeId: post.userId
+			}
+		}], {
+			_id: false
+		});
+
+		if (!silent) {
+			const note = await renderNote(user, post);
+			const content = renderCreate(note);
+			content['@context'] = context;
+
+			// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
+			if (data.reply && isLocalUser(user) && isRemoteUser(data.reply._user)) {
+				deliver(user, content, data.reply._user.account.inbox).save();
+			}
+
+			Promise.all(followers.map(follower => {
+				follower = follower.user[0];
+
+				if (isLocalUser(follower)) {
+					// Publish event to followers stream
+					stream(follower._id, 'post', postObj);
+				} else {
+					// フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信
+					if (isLocalUser(user)) {
+						deliver(user, content, follower.account.inbox).save();
+					}
+				}
+			}));
+		}
+	}
+
+	// チャンネルへの投稿
+	/* TODO
+	if (post.channelId) {
+		promises.push(
+			// Increment channel index(posts count)
+			Channel.update({ _id: post.channelId }, {
+				$inc: {
+					index: 1
+				}
+			}),
+
+			// Publish event to channel
+			promisedPostObj.then(postObj => {
+				publishChannelStream(post.channelId, 'post', postObj);
+			}),
+
+			Promise.all([
+				promisedPostObj,
+
+				// Get channel watchers
+				ChannelWatching.find({
+					channelId: post.channelId,
+					// 削除されたドキュメントは除く
+					deletedAt: { $exists: false }
+				})
+			]).then(([postObj, watches]) => {
+				// チャンネルの視聴者(のタイムライン)に配信
+				watches.forEach(w => {
+					stream(w.userId, 'post', postObj);
+				});
+			})
+		);
+	}*/
+
+	const mentions = [];
+
+	async function addMention(mentionee, reason) {
+		// Reject if already added
+		if (mentions.some(x => x.equals(mentionee))) return;
+
+		// Add mention
+		mentions.push(mentionee);
+
+		// Publish event
+		if (!user._id.equals(mentionee)) {
+			const mentioneeMutes = await Mute.find({
+				muter_id: mentionee,
+				deleted_at: { $exists: false }
+			});
+			const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId.toString());
+			if (mentioneesMutedUserIds.indexOf(user._id.toString()) == -1) {
+				event(mentionee, reason, postObj);
+				pushSw(mentionee, reason, postObj);
+			}
+		}
+	}
+
+	// If has in reply to post
+	if (data.reply) {
+		// Increment replies count
+		Post.update({ _id: data.reply._id }, {
+			$inc: {
+				repliesCount: 1
+			}
+		});
+
+		// (自分自身へのリプライでない限りは)通知を作成
+		notify(data.reply.userId, user._id, 'reply', {
+			postId: post._id
+		});
+
+		// Fetch watchers
+		PostWatching.find({
+			postId: data.reply._id,
+			userId: { $ne: user._id },
+			// 削除されたドキュメントは除く
+			deletedAt: { $exists: false }
+		}, {
+			fields: {
+				userId: true
+			}
+		}).then(watchers => {
+			watchers.forEach(watcher => {
+				notify(watcher.userId, user._id, 'reply', {
+					postId: post._id
+				});
+			});
+		});
+
+		// この投稿をWatchする
+		if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
+			watch(user._id, data.reply);
+		}
+
+		// Add mention
+		addMention(data.reply.userId, 'reply');
+	}
+
+	// If it is repost
+	if (data.repost) {
+		// Notify
+		const type = data.text ? 'quote' : 'repost';
+		notify(data.repost.userId, user._id, type, {
+			post_id: post._id
+		});
+
+		// Fetch watchers
+		PostWatching.find({
+			postId: data.repost._id,
+			userId: { $ne: user._id },
+			// 削除されたドキュメントは除く
+			deletedAt: { $exists: false }
+		}, {
+			fields: {
+				userId: true
+			}
+		}).then(watchers => {
+			watchers.forEach(watcher => {
+				notify(watcher.userId, user._id, type, {
+					postId: post._id
+				});
+			});
+		});
+
+		// この投稿をWatchする
+		if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
+			watch(user._id, data.repost);
+		}
+
+		// If it is quote repost
+		if (data.text) {
+			// Add mention
+			addMention(data.repost.userId, 'quote');
+		} else {
+			// Publish event
+			if (!user._id.equals(data.repost.userId)) {
+				event(data.repost.userId, 'repost', postObj);
+			}
+		}
+
+		// 今までで同じ投稿をRepostしているか
+		const existRepost = await Post.findOne({
+			userId: user._id,
+			repostId: data.repost._id,
+			_id: {
+				$ne: post._id
+			}
+		});
+
+		if (!existRepost) {
+			// Update repostee status
+			Post.update({ _id: data.repost._id }, {
+				$inc: {
+					repostCount: 1
+				}
+			});
+		}
+	}
+
+	// If has text content
+	if (data.text) {
+		// Extract an '@' mentions
+		const atMentions = tokens
+			.filter(t => t.type == 'mention')
+			.map(m => m.username)
+			// Drop dupulicates
+			.filter((v, i, s) => s.indexOf(v) == i);
+
+		// Resolve all mentions
+		await Promise.all(atMentions.map(async mention => {
+			// Fetch mentioned user
+			// SELECT _id
+			const mentionee = await User
+				.findOne({
+					usernameLower: mention.toLowerCase()
+				}, { _id: true });
+
+			// When mentioned user not found
+			if (mentionee == null) return;
+
+			// 既に言及されたユーザーに対する返信や引用repostの場合も無視
+			if (data.reply && data.reply.userId.equals(mentionee._id)) return;
+			if (data.repost && data.repost.userId.equals(mentionee._id)) return;
+
+			// Add mention
+			addMention(mentionee._id, 'mention');
+
+			// Create notification
+			notify(mentionee._id, user._id, 'mention', {
+				post_id: post._id
+			});
+		}));
+	}
+
+	// Append mentions data
+	if (mentions.length > 0) {
+		Post.update({ _id: post._id }, {
+			$set: {
+				mentions
+			}
+		});
+	}
+});
diff --git a/src/services/post/reaction/create.ts b/src/services/post/reaction/create.ts
new file mode 100644
index 0000000000..c26efcfc75
--- /dev/null
+++ b/src/services/post/reaction/create.ts
@@ -0,0 +1,94 @@
+import { IUser, pack as packUser, isLocalUser, isRemoteUser } from '../../../models/user';
+import Post, { IPost, pack as packPost } from '../../../models/post';
+import PostReaction from '../../../models/post-reaction';
+import { publishPostStream } from '../../../publishers/stream';
+import notify from '../../../publishers/notify';
+import pushSw from '../../../publishers/push-sw';
+import PostWatching from '../../../models/post-watching';
+import watch from '../watch';
+import renderLike from '../../../remote/activitypub/renderer/like';
+import { deliver } from '../../../queue';
+import context from '../../../remote/activitypub/renderer/context';
+
+export default async (user: IUser, post: IPost, reaction: string) => new Promise(async (res, rej) => {
+	// Myself
+	if (post.userId.equals(user._id)) {
+		return rej('cannot react to my post');
+	}
+
+	// if already reacted
+	const exist = await PostReaction.findOne({
+		postId: post._id,
+		userId: user._id
+	});
+
+	if (exist !== null) {
+		return rej('already reacted');
+	}
+
+	// Create reaction
+	await PostReaction.insert({
+		createdAt: new Date(),
+		postId: post._id,
+		userId: user._id,
+		reaction
+	});
+
+	res();
+
+	const inc = {};
+	inc[`reactionCounts.${reaction}`] = 1;
+
+	// Increment reactions count
+	await Post.update({ _id: post._id }, {
+		$inc: inc
+	});
+
+	publishPostStream(post._id, 'reacted');
+
+	// Notify
+	notify(post.userId, user._id, 'reaction', {
+		postId: post._id,
+		reaction: reaction
+	});
+
+	pushSw(post.userId, 'reaction', {
+		user: await packUser(user, post.userId),
+		post: await packPost(post, post.userId),
+		reaction: reaction
+	});
+
+	// Fetch watchers
+	PostWatching
+		.find({
+			postId: post._id,
+			userId: { $ne: user._id }
+		}, {
+			fields: {
+				userId: true
+			}
+		})
+		.then(watchers => {
+			watchers.forEach(watcher => {
+				notify(watcher.userId, user._id, 'reaction', {
+					postId: post._id,
+					reaction: reaction
+				});
+			});
+		});
+
+	// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
+	if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
+		watch(user._id, post);
+	}
+
+	//#region 配信
+	const content = renderLike(user, post);
+	content['@context'] = context;
+
+	// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
+	if (isLocalUser(user) && isRemoteUser(post._user)) {
+		deliver(user, content, post._user.account.inbox).save();
+	}
+	//#endregion
+});
diff --git a/src/post/watch.ts b/src/services/post/watch.ts
similarity index 90%
rename from src/post/watch.ts
rename to src/services/post/watch.ts
index 61ea444430..bbd9976f40 100644
--- a/src/post/watch.ts
+++ b/src/services/post/watch.ts
@@ -1,5 +1,5 @@
 import * as mongodb from 'mongodb';
-import Watching from '../models/post-watching';
+import Watching from '../../models/post-watching';
 
 export default async (me: mongodb.ObjectID, post: object) => {
 	// 自分の投稿はwatchできない