From 6a5c6280ffd3ffe820beb23294f1c2c1f5deb9cf Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 7 Oct 2017 03:36:46 +0900
Subject: [PATCH] :v:

---
 src/api/bot/core.ts                           | 88 +++++++++++++++++++
 src/api/bot/interfaces/line.ts                | 37 ++++++++
 src/api/endpoints/i/appdata/set.ts            |  2 +-
 src/api/models/user.ts                        |  3 +
 src/api/serializers/user.ts                   |  1 +
 src/api/server.ts                             |  2 +
 .../scripts => common}/get-post-summary.js    |  4 +
 src/config.ts                                 |  3 +
 src/web/app/desktop/script.js                 |  2 +-
 src/web/app/desktop/tags/notifications.tag    |  2 +-
 src/web/app/desktop/tags/pages/home.tag       |  2 +-
 .../app/mobile/tags/notification-preview.tag  |  2 +-
 src/web/app/mobile/tags/notification.tag      |  2 +-
 src/web/app/mobile/tags/notifications.tag     |  2 +-
 src/web/app/mobile/tags/page/home.tag         |  2 +-
 src/web/app/mobile/tags/post-detail.tag       |  2 +-
 src/web/app/mobile/tags/timeline.tag          |  2 +-
 src/web/app/mobile/tags/user.tag              |  2 +-
 tslint.json                                   |  1 +
 19 files changed, 150 insertions(+), 11 deletions(-)
 create mode 100644 src/api/bot/core.ts
 create mode 100644 src/api/bot/interfaces/line.ts
 rename src/{web/app/common/scripts => common}/get-post-summary.js (88%)

diff --git a/src/api/bot/core.ts b/src/api/bot/core.ts
new file mode 100644
index 000000000..002ac1b06
--- /dev/null
+++ b/src/api/bot/core.ts
@@ -0,0 +1,88 @@
+import * as EventEmitter from 'events';
+import * as bcrypt from 'bcryptjs';
+
+import User, { IUser } from '../models/user';
+
+export default class BotCore extends EventEmitter {
+	public user: IUser;
+
+	private context: Context = null;
+
+	constructor(user: IUser) {
+		super();
+
+		this.user = user;
+	}
+
+	public async q(query: string): Promise<string> {
+		if (this.context != null) {
+			return await this.context.q(query);
+		}
+
+		switch (query) {
+			case 'ping':
+				return 'PONG';
+			case 'ログイン':
+			case 'サインイン':
+				this.context = new SigninContext(this);
+				return await this.context.greet();
+			default:
+				return '?';
+		}
+	}
+
+	public setUser(user: IUser) {
+		this.user = user;
+		this.emit('set-user', user);
+	}
+}
+
+abstract class Context {
+	protected core: BotCore;
+
+	public abstract async greet(): Promise<string>;
+	public abstract async q(query: string): Promise<string>;
+
+	constructor(core: BotCore) {
+		this.core = core;
+	}
+}
+
+class SigninContext extends Context {
+	private temporaryUser: IUser;
+
+	public async greet(): Promise<string> {
+		return 'まずユーザー名を教えてください:';
+	}
+
+	public async q(query: string): Promise<string> {
+		if (this.temporaryUser == null) {
+			// Fetch user
+			const user: IUser = await User.findOne({
+				username_lower: query.toLowerCase()
+			}, {
+				fields: {
+					data: false,
+					profile: false
+				}
+			});
+
+			if (user === null) {
+				return `${query}というユーザーは存在しませんでした... もう一度教えてください:`;
+			} else {
+				this.temporaryUser = user;
+				return `パスワードを教えてください:`;
+			}
+		} else {
+			// Compare password
+			const same = bcrypt.compareSync(query, this.temporaryUser.password);
+
+			if (same) {
+				this.core.setUser(this.temporaryUser);
+				return `${this.temporaryUser.name}さん、おかえりなさい!`;
+			} else {
+				return `パスワードが違います... もう一度教えてください:`;
+			}
+		}
+	}
+}
diff --git a/src/api/bot/interfaces/line.ts b/src/api/bot/interfaces/line.ts
new file mode 100644
index 000000000..4bee844c1
--- /dev/null
+++ b/src/api/bot/interfaces/line.ts
@@ -0,0 +1,37 @@
+import * as EventEmitter from 'events';
+import * as express from 'express';
+import * as crypto from 'crypto';
+//import User from '../../models/user';
+import config from '../../../conf';
+/*import BotCore from '../core';
+
+const sessions: {
+	userId: string;
+	session: BotCore;
+}[] = [];
+*/
+module.exports = async (app: express.Application) => {
+	if (config.line_bot == null) return;
+
+	const handler = new EventEmitter();
+
+	app.post('/hooks/line', (req, res, next) => {
+		// req.headers['X-Line-Signature'] は常に string ですが、型定義の都合上
+		// string | string[] になっているので string を明示しています
+		const sig1 = req.headers['X-Line-Signature'] as string;
+
+		const hash = crypto.createHmac('sha256', config.line_bot.channel_secret)
+			.update(JSON.stringify(req.body));
+
+		const sig2 = hash.digest('base64');
+
+		// シグネチャ比較
+		if (sig1 === sig2) {
+			console.log(req.body);
+			handler.emit(req.body.type);
+			res.sendStatus(200);
+		} else {
+			res.sendStatus(400);
+		}
+	});
+};
diff --git a/src/api/endpoints/i/appdata/set.ts b/src/api/endpoints/i/appdata/set.ts
index 24f192de6..9c3dbe185 100644
--- a/src/api/endpoints/i/appdata/set.ts
+++ b/src/api/endpoints/i/appdata/set.ts
@@ -21,7 +21,7 @@ module.exports = (params, user, app, isSecure) => new Promise(async (res, rej) =
 	const [data, dataError] = $(params.data).optional.object()
 		.pipe(obj => {
 			const hasInvalidData = Object.entries(obj).some(([k, v]) =>
-				$(k).string().match(/^[a-z_]+$/).isNg() && $(v).string().isNg());
+				$(k).string().match(/^[a-z_]+$/).nok() && $(v).string().nok());
 			return !hasInvalidData;
 		}).$;
 	if (dataError) return rej('invalid data param');
diff --git a/src/api/models/user.ts b/src/api/models/user.ts
index 1591b339b..4f8086d42 100644
--- a/src/api/models/user.ts
+++ b/src/api/models/user.ts
@@ -57,6 +57,9 @@ export type IUser = {
 		user_id: string;
 		screen_name: string;
 	};
+	line: {
+		user_id: string;
+	};
 	description: string;
 	profile: {
 		location: string;
diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts
index 23a176096..3deff2d00 100644
--- a/src/api/serializers/user.ts
+++ b/src/api/serializers/user.ts
@@ -79,6 +79,7 @@ export default (
 		delete _user.twitter.access_token;
 		delete _user.twitter.access_token_secret;
 	}
+	delete _user.line;
 
 	// Visible via only the official client
 	if (!opts.includeSecrets) {
diff --git a/src/api/server.ts b/src/api/server.ts
index c98167eb3..fdff0c754 100644
--- a/src/api/server.ts
+++ b/src/api/server.ts
@@ -54,4 +54,6 @@ app.use((req, res, next) => {
 require('./service/github')(app);
 require('./service/twitter')(app);
 
+require('./bot/interfaces/line')(app);
+
 module.exports = app;
diff --git a/src/web/app/common/scripts/get-post-summary.js b/src/common/get-post-summary.js
similarity index 88%
rename from src/web/app/common/scripts/get-post-summary.js
rename to src/common/get-post-summary.js
index 83eda8f6b..f7a481a16 100644
--- a/src/web/app/common/scripts/get-post-summary.js
+++ b/src/common/get-post-summary.js
@@ -1,3 +1,7 @@
+/**
+ * 投稿を表す文字列を取得します。
+ * @param {*} post 投稿
+ */
 const summarize = post => {
 	let summary = post.text ? post.text : '';
 
diff --git a/src/config.ts b/src/config.ts
index f8facdee2..0ea332f67 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -68,6 +68,9 @@ type Source = {
 		hook_secret: string;
 		username: string;
 	};
+	line_bot?: {
+		channel_secret: string;
+	};
 	analysis?: {
 		mecab_command?: string;
 	};
diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.js
index 2e8114794..e3dc8b7d9 100644
--- a/src/web/app/desktop/script.js
+++ b/src/web/app/desktop/script.js
@@ -11,7 +11,7 @@ import * as riot from 'riot';
 import init from '../init';
 import route from './router';
 import fuckAdBlock from './scripts/fuck-ad-block';
-import getPostSummary from '../common/scripts/get-post-summary';
+import getPostSummary from '../../../common/get-post-summary';
 
 /**
  * init
diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag
index 21e4fe7fa..4747d1c0f 100644
--- a/src/web/app/desktop/tags/notifications.tag
+++ b/src/web/app/desktop/tags/notifications.tag
@@ -207,7 +207,7 @@
 
 	</style>
 	<script>
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		this.getPostSummary = getPostSummary;
 
 		this.mixin('i');
diff --git a/src/web/app/desktop/tags/pages/home.tag b/src/web/app/desktop/tags/pages/home.tag
index 124a2eefa..a56c54605 100644
--- a/src/web/app/desktop/tags/pages/home.tag
+++ b/src/web/app/desktop/tags/pages/home.tag
@@ -8,7 +8,7 @@
 	</style>
 	<script>
 		import Progress from '../../../common/scripts/loading';
-		import getPostSummary from '../../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../../common/get-post-summary';
 
 		this.mixin('i');
 		this.mixin('api');
diff --git a/src/web/app/mobile/tags/notification-preview.tag b/src/web/app/mobile/tags/notification-preview.tag
index 077ae7846..36b4f5eda 100644
--- a/src/web/app/mobile/tags/notification-preview.tag
+++ b/src/web/app/mobile/tags/notification-preview.tag
@@ -110,7 +110,7 @@
 
 	</style>
 	<script>
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		this.getPostSummary = getPostSummary;
 		this.notification = this.opts.notification;
 	</script>
diff --git a/src/web/app/mobile/tags/notification.tag b/src/web/app/mobile/tags/notification.tag
index 366370952..416493ee2 100644
--- a/src/web/app/mobile/tags/notification.tag
+++ b/src/web/app/mobile/tags/notification.tag
@@ -163,7 +163,7 @@
 
 	</style>
 	<script>
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		this.getPostSummary = getPostSummary;
 		this.notification = this.opts.notification;
 	</script>
diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag
index 2f314769d..9985b3351 100644
--- a/src/web/app/mobile/tags/notifications.tag
+++ b/src/web/app/mobile/tags/notifications.tag
@@ -78,7 +78,7 @@
 
 	</style>
 	<script>
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		this.getPostSummary = getPostSummary;
 
 		this.mixin('api');
diff --git a/src/web/app/mobile/tags/page/home.tag b/src/web/app/mobile/tags/page/home.tag
index efb5068a5..6f7369798 100644
--- a/src/web/app/mobile/tags/page/home.tag
+++ b/src/web/app/mobile/tags/page/home.tag
@@ -9,7 +9,7 @@
 	<script>
 		import ui from '../../scripts/ui-event';
 		import Progress from '../../../common/scripts/loading';
-		import getPostSummary from '../../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../../common/get-post-summary';
 		import openPostForm from '../../scripts/open-post-form';
 
 		this.mixin('i');
diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag
index dc032fe96..4be1a8080 100644
--- a/src/web/app/mobile/tags/post-detail.tag
+++ b/src/web/app/mobile/tags/post-detail.tag
@@ -264,7 +264,7 @@
 	</style>
 	<script>
 		import compile from '../../common/scripts/text-compiler';
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		import openPostForm from '../scripts/open-post-form';
 
 		this.mixin('api');
diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag
index 2b0948ac3..80debbf66 100644
--- a/src/web/app/mobile/tags/timeline.tag
+++ b/src/web/app/mobile/tags/timeline.tag
@@ -464,7 +464,7 @@
 	</style>
 	<script>
 		import compile from '../../common/scripts/text-compiler';
-		import getPostSummary from '../../common/scripts/get-post-summary';
+		import getPostSummary from '../../../../common/get-post-summary';
 		import openPostForm from '../scripts/open-post-form';
 
 		this.mixin('api');
diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag
index f29f0a0c8..cc3407421 100644
--- a/src/web/app/mobile/tags/user.tag
+++ b/src/web/app/mobile/tags/user.tag
@@ -428,7 +428,7 @@
 
 	</style>
 	<script>
-		import summary from '../../common/scripts/get-post-summary';
+		import summary from '../../../../common/get-post-summary';
 
 		this.post = this.opts.post;
 		this.text = summary(this.post);
diff --git a/tslint.json b/tslint.json
index dfd830967..33704ca43 100644
--- a/tslint.json
+++ b/tslint.json
@@ -23,6 +23,7 @@
 		"comment-format": [false],
 		"interface-over-type-literal": false,
 		"max-line-length": [false],
+		"max-classes-per-file": false,
 		"member-ordering": [false],
 		"ban-types": [
 			"Object"