From f8c414aafca8e91017cf01a65c21b57c88783076 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Mon, 21 May 2018 11:08:08 +0900
Subject: [PATCH] #1621

---
 locales/ja.yml                                |  3 +++
 .../desktop/views/components/notes.note.vue   |  4 +++-
 .../views/components/settings.profile.vue     |  8 +++++++-
 .../app/mobile/views/components/note.vue      |  4 +++-
 .../views/pages/settings/settings.profile.vue | 20 +++++++++++--------
 src/models/note.ts                            |  4 ++++
 src/models/user.ts                            |  1 +
 src/server/api/endpoints/i/update.ts          |  6 ++++++
 8 files changed, 39 insertions(+), 11 deletions(-)

diff --git a/locales/ja.yml b/locales/ja.yml
index 3e3cfd500..960ff5fa6 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -550,6 +550,8 @@ desktop/views/components/settings.profile.vue:
   description: "自己紹介"
   birthday: "誕生日"
   save: "保存"
+  is-bot: "このアカウントはBotです"
+  is-cat: "このアカウントはCatです"
 
 desktop/views/components/sub-note-content.vue:
   hidden: "(この投稿は非公開です)"
@@ -814,6 +816,7 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-bot: "このアカウントはBotです"
+  is-cat: "このアカウントはCatです"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue
index f6eba4902..735fa39f4 100644
--- a/src/client/app/desktop/views/components/notes.note.vue
+++ b/src/client/app/desktop/views/components/notes.note.vue
@@ -16,7 +16,8 @@
 		<div class="main">
 			<header>
 				<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link>
-				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
+				<span class="is-bot" v-if="p.user.isBot">bot</span>
+				<span class="is-cat" v-if="p.user.isCat">cat</span>
 				<span class="username"><mk-acct :user="p.user"/></span>
 				<div class="info">
 					<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
@@ -431,6 +432,7 @@ root(isDark)
 						text-decoration underline
 
 				> .is-bot
+				> .is-cat
 					margin 0 .5em 0 0
 					padding 1px 6px
 					font-size 12px
diff --git a/src/client/app/desktop/views/components/settings.profile.vue b/src/client/app/desktop/views/components/settings.profile.vue
index 84b09eb98..132ab12f1 100644
--- a/src/client/app/desktop/views/components/settings.profile.vue
+++ b/src/client/app/desktop/views/components/settings.profile.vue
@@ -24,7 +24,8 @@
 	<button class="ui primary" @click="save">%i18n:@save%</button>
 	<section>
 		<h2>その他</h2>
-		<mk-switch v-model="os.i.isBot" @change="onChangeIsBot" text="このアカウントはbotです"/>
+		<mk-switch v-model="os.i.isBot" @change="onChangeIsBot" text="%i18n:@is-bot%"/>
+		<mk-switch v-model="os.i.isCat" @change="onChangeIsCat" text="%i18n:@is-cat%"/>
 	</section>
 </div>
 </template>
@@ -65,6 +66,11 @@ export default Vue.extend({
 			(this as any).api('i/update', {
 				isBot: (this as any).os.i.isBot
 			});
+		},
+		onChangeIsCat() {
+			(this as any).api('i/update', {
+				isCat: (this as any).os.i.isCat
+			});
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
index b940b6b20..7a8457e56 100644
--- a/src/client/app/mobile/views/components/note.vue
+++ b/src/client/app/mobile/views/components/note.vue
@@ -17,7 +17,8 @@
 			<header>
 				<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle == 'smart'"/>
 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
-				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
+				<span class="is-bot" v-if="p.user.isBot">bot</span>
+				<span class="is-cat" v-if="p.user.isCat">cat</span>
 				<span class="username"><mk-acct :user="p.user"/></span>
 				<div class="info">
 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
@@ -386,6 +387,7 @@ root(isDark)
 						text-decoration underline
 
 				> .is-bot
+				> .is-cat
 					margin 0 0.5em 0 0
 					padding 1px 6px
 					font-size 12px
diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue
index 260cccbae..796beedf4 100644
--- a/src/client/app/mobile/views/pages/settings/settings.profile.vue
+++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue
@@ -50,7 +50,11 @@
 					md-content="%18n:!@uploading%"/>
 
 			<div>
-				<md-switch v-model="os.i.isBot" @change="onChangeIsBot">%i18n:@is-bot%</md-switch>
+				<md-switch v-model="isBot">%i18n:@is-bot%</md-switch>
+			</div>
+
+			<div>
+				<md-switch v-model="isCat">%i18n:@is-cat%</md-switch>
 			</div>
 		</md-card-content>
 
@@ -75,6 +79,8 @@ export default Vue.extend({
 			birthday: null,
 			avatarId: null,
 			bannerId: null,
+			isBot: false,
+			isCat: false,
 			saving: false,
 			uploading: false
 		};
@@ -88,15 +94,11 @@ export default Vue.extend({
 		this.birthday = (this as any).os.i.profile.birthday;
 		this.avatarId = (this as any).os.i.avatarId;
 		this.bannerId = (this as any).os.i.bannerId;
+		this.isBot = (this as any).os.i.isBot;
+		this.isCat = (this as any).os.i.isCat;
 	},
 
 	methods: {
-		onChangeIsBot() {
-			(this as any).api('i/update', {
-				isBot: (this as any).os.i.isBot
-			});
-		},
-
 		onAvatarChange([file]) {
 			this.uploading = true;
 
@@ -150,7 +152,9 @@ export default Vue.extend({
 				description: this.description || null,
 				birthday: this.birthday || null,
 				avatarId: this.avatarId,
-				bannerId: this.bannerId
+				bannerId: this.bannerId,
+				isBot: this.isBot,
+				isCat: this.isCat
 			}).then(i => {
 				this.saving = false;
 				(this as any).os.i.avatarId = i.avatarId;
diff --git a/src/models/note.ts b/src/models/note.ts
index 507092336..1274901d4 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -324,6 +324,10 @@ export const pack = async (
 	// resolve promises in _note object
 	_note = await rap(_note);
 
+	if (_note.user.isCat && _note.text) {
+		_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ');
+	}
+
 	if (hide) {
 		_note.mediaIds = [];
 		_note.text = null;
diff --git a/src/models/user.ts b/src/models/user.ts
index 108111cec..477bb232e 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -77,6 +77,7 @@ export interface ILocalUser extends IUserBase {
 	};
 	lastUsedAt: Date;
 	isBot: boolean;
+	isCat: boolean;
 	isPro: boolean;
 	twoFactorSecret: string;
 	twoFactorEnabled: boolean;
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index b7b25d0f6..6e0c5b851 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -47,6 +47,11 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => {
 	if (isBotErr) return rej('invalid isBot param');
 	if (isBot != null) user.isBot = isBot;
 
+	// Get 'isCat' parameter
+	const [isCat, isCatErr] = $.bool.optional().get(params.isCat);
+	if (isCatErr) return rej('invalid isCat param');
+	if (isCat != null) user.isCat = isCat;
+
 	// Get 'autoWatch' parameter
 	const [autoWatch, autoWatchErr] = $.bool.optional().get(params.autoWatch);
 	if (autoWatchErr) return rej('invalid autoWatch param');
@@ -82,6 +87,7 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => {
 			bannerColor: user.bannerColor,
 			profile: user.profile,
 			isBot: user.isBot,
+			isCat: user.isCat,
 			settings: user.settings
 		}
 	});