From bd207b5012568a70403104208b77b185e88ddd6a Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 19 Apr 2018 18:54:34 +0900
Subject: [PATCH] Implement suspend account

---
 cli/suspend.js                                 | 18 ++++++++++++++++++
 locales/en.yml                                 |  2 ++
 locales/fr.yml                                 |  2 ++
 locales/ja.yml                                 |  2 ++
 .../desktop/views/pages/user/user.header.vue   | 12 ++++++++++--
 src/client/app/mobile/views/pages/user.vue     | 12 ++++++++++--
 src/remote/activitypub/models/image.ts         |  5 +++++
 src/remote/activitypub/models/note.ts          |  5 +++++
 8 files changed, 54 insertions(+), 4 deletions(-)
 create mode 100644 cli/suspend.js

diff --git a/cli/suspend.js b/cli/suspend.js
new file mode 100644
index 0000000000..0f22bba477
--- /dev/null
+++ b/cli/suspend.js
@@ -0,0 +1,18 @@
+const mongo = require('mongodb');
+const User = require('../built/models/user').default;
+
+const args = process.argv.slice(2);
+
+const userId = new mongo.ObjectID(args[0]);
+
+console.log(`Suspending ${userId}...`);
+
+User.update({ _id: userId }, {
+	$set: {
+		isSuspended: true
+	}
+}).then(() => {
+	console.log(`Suspended ${userId}`);
+}, e => {
+	console.error(e);
+});
diff --git a/locales/en.yml b/locales/en.yml
index 4eb0a3446c..81357c075c 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -406,6 +406,7 @@ desktop/views/pages/user/user.friends.vue:
   no-users: "No users"
 
 desktop/views/pages/user/user.header.vue:
+  is-suspended: "This account has been suspended."
   is-remote: "This user is a remote user, so the information is not accurate. "
   view-remote: "See accurate information"
 
@@ -594,6 +595,7 @@ mobile/views/pages/user.vue:
   overview: "Overview"
   timeline: "Timeline"
   media: "Media"
+  is-suspended: "This account has been suspended."
   is-remote: "This user is a remote user, so the information is not accurate. "
   view-remote: "See accurate information"
 
diff --git a/locales/fr.yml b/locales/fr.yml
index 0e0019e565..8fb7543446 100644
--- a/locales/fr.yml
+++ b/locales/fr.yml
@@ -406,6 +406,7 @@ desktop/views/pages/user/user.friends.vue:
   no-users: "Pas d'utilisateurs"
 
 desktop/views/pages/user/user.header.vue:
+  is-suspended: "This account has been suspended."
   is-remote: "Cet utilisateur n'est pas un utilisateur de Misskey. Certaines informations peuvent être erronées "
   view-remote: "Voir les informations détaillées"
 
@@ -594,6 +595,7 @@ mobile/views/pages/user.vue:
   overview: "Aperçu"
   timeline: "Fil d'actualité"
   media: "Media"
+  is-suspended: "This account has been suspended."
   is-remote: "Cet utilisateur n'est pas un utilisateur de Misskey. Certaines informations peuvent être erronées "
   view-remote: "Voir les informations détaillées"
 
diff --git a/locales/ja.yml b/locales/ja.yml
index f5d304349e..2f69a6f3d3 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -406,6 +406,7 @@ desktop/views/pages/user/user.friends.vue:
   no-users: "よく話すユーザーはいません"
 
 desktop/views/pages/user/user.header.vue:
+  is-suspended: "このユーザーは凍結されています。"
   is-remote: "このユーザーはリモートユーザーです。"
   view-remote: "正確な情報を見る"
 
@@ -594,6 +595,7 @@ mobile/views/pages/user.vue:
   overview: "概要"
   timeline: "タイムライン"
   media: "メディア"
+  is-suspended: "このユーザーは凍結されています。"
   is-remote: "このユーザーはリモートユーザーです。"
   view-remote: "正確な情報を見る"
 
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 90bedf7161..5c94c86889 100644
--- a/src/client/app/desktop/views/pages/user/user.header.vue
+++ b/src/client/app/desktop/views/pages/user/user.header.vue
@@ -1,5 +1,6 @@
 <template>
 <div class="header" :data-is-dark-background="user.bannerUrl != null">
+	<div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div>
 	<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div>
 	<div class="banner-container" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=2048)` : ''">
 		<div class="banner" ref="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=2048)` : ''" @click="onBannerClick"></div>
@@ -73,10 +74,17 @@ export default Vue.extend({
 	background #f7f7f7
 	box-shadow 0 1px 1px rgba(0, 0, 0, 0.075)
 
+	> .is-suspended
 	> .is-remote
 		padding 16px
-		color #573c08
-		background #fff0db
+
+		&.is-suspended
+			color #570808
+			background #ffdbdb
+
+		&.is-remote
+			color #573c08
+			background #fff0db
 
 		> p
 			margin 0 auto
diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue
index b614d4396f..db0b45ac6e 100644
--- a/src/client/app/mobile/views/pages/user.vue
+++ b/src/client/app/mobile/views/pages/user.vue
@@ -2,6 +2,7 @@
 <mk-ui>
 	<span slot="header" v-if="!fetching">%fa:user% {{ user | userName }}</span>
 	<main v-if="!fetching">
+		<div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div>
 		<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div>
 		<header>
 			<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"></div>
@@ -110,10 +111,17 @@ export default Vue.extend({
 @import '~const.styl'
 
 main
+	> .is-suspended
 	> .is-remote
 		padding 16px
-		color #573c08
-		background #fff0db
+
+		&.is-suspended
+			color #570808
+			background #ffdbdb
+
+		&.is-remote
+			color #573c08
+			background #fff0db
 
 		> p
 			margin 0 auto
diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts
index d7bc5aff2f..3f522f9b52 100644
--- a/src/remote/activitypub/models/image.ts
+++ b/src/remote/activitypub/models/image.ts
@@ -11,6 +11,11 @@ const log = debug('misskey:activitypub');
  * Imageを作成します。
  */
 export async function createImage(actor: IRemoteUser, value): Promise<IDriveFile> {
+	// 投稿者が凍結されていたらスキップ
+	if (actor.isSuspended) {
+		return null;
+	}
+
 	const image = await new Resolver().resolve(value);
 
 	if (image.url == null) {
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index 94d723e239..f830370a23 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -58,6 +58,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 	// 投稿者をフェッチ
 	const actor = await resolvePerson(note.attributedTo) as IRemoteUser;
 
+	// 投稿者が凍結されていたらスキップ
+	if (actor.isSuspended) {
+		return null;
+	}
+
 	//#region Visibility
 	let visibility = 'public';
 	if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted';