mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-22 21:45:13 +01:00
Merge branch 'master' of https://github.com/syuilo/misskey
This commit is contained in:
commit
fabcad6db9
36 changed files with 1037 additions and 54 deletions
|
@ -2,6 +2,14 @@ ChangeLog (Release Notes)
|
|||
=========================
|
||||
主に notable な changes を書いていきます
|
||||
|
||||
2735 (2017/10/22)
|
||||
-----------------
|
||||
* モバイル版からでもクライアントバージョンを確認できるように
|
||||
|
||||
2732 (2017/10/22)
|
||||
-----------------
|
||||
* 依存関係の更新など
|
||||
|
||||
2584 (2017/09/08)
|
||||
-----------------
|
||||
* New: ユーザーページによく使うドメインを表示 (#771)
|
||||
|
|
15
README.md
15
README.md
|
@ -25,7 +25,8 @@ and more! You can touch with your own eyes at https://misskey.xyz/.
|
|||
|
||||
Setup and Installation
|
||||
----------------------------------------------------------------
|
||||
Please see [Setup and installation guide](./docs/setup.en.md).
|
||||
If you want to run your own instance of Misskey,
|
||||
please see [Setup and installation guide](./docs/setup.en.md).
|
||||
|
||||
Contribution
|
||||
----------------------------------------------------------------
|
||||
|
@ -42,14 +43,6 @@ If you want to donate to Misskey, please get in touch with [@syuilo][syuilo-link
|
|||
|
||||
**Note:** When you donate to Misskey, your name will be displayed in [donors](./DONORS.md).
|
||||
|
||||
Collaborators
|
||||
----------------------------------------------------------------
|
||||
| ![syuilo][syuilo-icon] | ![Morisawa Aya][ayamorisawa-icon] | ![otofune][otofune-icon] |
|
||||
|------------------------|-----------------------------------|---------------------------------|
|
||||
| [syuilo][syuilo-link] | [Aya Morisawa][ayamorisawa-link] | [otofune][otofune-link] |
|
||||
|
||||
[List of all contributors](https://github.com/syuilo/misskey/graphs/contributors)
|
||||
|
||||
Copyright
|
||||
----------------------------------------------------------------
|
||||
Misskey is an open-source software licensed under [The MIT License](LICENSE).
|
||||
|
@ -67,7 +60,3 @@ Misskey is an open-source software licensed under [The MIT License](LICENSE).
|
|||
<!-- Collaborators Info -->
|
||||
[syuilo-link]: https://syuilo.com
|
||||
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
|
||||
[ayamorisawa-link]: https://github.com/ayamorisawa
|
||||
[ayamorisawa-icon]: https://avatars0.githubusercontent.com/u/10798641?v=3&s=70
|
||||
[otofune-link]: https://github.com/otofune
|
||||
[otofune-icon]: https://avatars0.githubusercontent.com/u/15062473?v=3&s=70
|
||||
|
|
|
@ -64,7 +64,7 @@ common:
|
|||
|
||||
mk-error:
|
||||
title: "Unable to connect to the server"
|
||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
||||
description: "There is a problem with Internet connection, or the server may be down or maintaining. Please {try again} later."
|
||||
thanks: "Thank you for using Misskey."
|
||||
|
||||
mk-forkit:
|
||||
|
|
|
@ -64,7 +64,7 @@ common:
|
|||
|
||||
mk-error:
|
||||
title: "サーバーに接続できません"
|
||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
|
||||
thanks: "いつもMisskeyをご利用いただきありがとうございます。"
|
||||
|
||||
mk-forkit:
|
||||
|
|
31
package.json
31
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "0.0.2584",
|
||||
"version": "0.0.2735",
|
||||
"license": "MIT",
|
||||
"description": "A miniblog-based SNS",
|
||||
"bugs": "https://github.com/syuilo/misskey/issues",
|
||||
|
@ -48,30 +48,31 @@
|
|||
"@types/is-url": "1.2.28",
|
||||
"@types/js-yaml": "3.9.0",
|
||||
"@types/mocha": "2.2.43",
|
||||
"@types/mongodb": "2.2.11",
|
||||
"@types/mongodb": "2.2.13",
|
||||
"@types/monk": "1.0.6",
|
||||
"@types/morgan": "1.7.33",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/multer": "1.3.2",
|
||||
"@types/node": "8.0.31",
|
||||
"@types/node": "8.0.33",
|
||||
"@types/ratelimiter": "2.1.28",
|
||||
"@types/redis": "2.6.0",
|
||||
"@types/request": "2.0.3",
|
||||
"@types/request": "2.0.4",
|
||||
"@types/rimraf": "2.0.0",
|
||||
"@types/riot": "3.6.0",
|
||||
"@types/serve-favicon": "2.2.28",
|
||||
"@types/uuid": "3.4.2",
|
||||
"@types/webpack": "3.0.12",
|
||||
"@types/webpack": "3.0.13",
|
||||
"@types/webpack-stream": "3.2.7",
|
||||
"@types/websocket": "0.0.34",
|
||||
"awesome-typescript-loader": "3.2.3",
|
||||
"chai": "4.1.2",
|
||||
"chai-http": "3.0.0",
|
||||
"css-loader": "0.28.7",
|
||||
"event-stream": "3.3.4",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cssnano": "2.1.2",
|
||||
"gulp-imagemin": "3.3.0",
|
||||
"gulp-htmlmin": "3.0.0",
|
||||
"gulp-imagemin": "3.4.0",
|
||||
"gulp-mocha": "4.3.1",
|
||||
"gulp-pug": "3.3.0",
|
||||
"gulp-rename": "1.2.2",
|
||||
|
@ -83,15 +84,15 @@
|
|||
"mocha": "3.5.3",
|
||||
"riot-tag-loader": "1.0.0",
|
||||
"string-replace-webpack-plugin": "0.1.3",
|
||||
"style-loader": "0.18.2",
|
||||
"style-loader": "0.19.0",
|
||||
"stylus": "0.54.5",
|
||||
"stylus-loader": "3.0.1",
|
||||
"swagger-jsdoc": "1.9.7",
|
||||
"tslint": "5.7.0",
|
||||
"uglify-es": "3.0.27",
|
||||
"uglify-es-webpack-plugin": "0.10.0",
|
||||
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
|
||||
"webpack": "3.6.0"
|
||||
"uglifyjs-webpack-plugin": "1.0.0-beta.2",
|
||||
"webpack": "3.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"accesses": "2.5.0",
|
||||
|
@ -103,12 +104,12 @@
|
|||
"chalk": "2.1.0",
|
||||
"compression": "1.7.1",
|
||||
"cors": "2.8.4",
|
||||
"cropperjs": "1.0.0",
|
||||
"cropperjs": "1.1.3",
|
||||
"crypto": "1.0.1",
|
||||
"debug": "3.1.0",
|
||||
"deep-equal": "1.0.1",
|
||||
"deepcopy": "0.6.3",
|
||||
"diskusage": "^0.2.2",
|
||||
"diskusage": "0.2.2",
|
||||
"download": "6.2.5",
|
||||
"elasticsearch": "13.3.1",
|
||||
"escape-regexp": "0.0.1",
|
||||
|
@ -122,8 +123,8 @@
|
|||
"js-yaml": "3.10.0",
|
||||
"mecab-async": "^0.1.0",
|
||||
"moji": "^0.5.1",
|
||||
"mongodb": "2.2.31",
|
||||
"monk": "6.0.4",
|
||||
"mongodb": "2.2.33",
|
||||
"monk": "6.0.5",
|
||||
"morgan": "1.9.0",
|
||||
"ms": "2.0.0",
|
||||
"multer": "1.3.0",
|
||||
|
@ -139,7 +140,7 @@
|
|||
"redis": "2.8.0",
|
||||
"request": "2.83.0",
|
||||
"rimraf": "2.6.2",
|
||||
"riot": "3.7.2",
|
||||
"riot": "3.7.3",
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.0",
|
||||
"serve-favicon": "2.4.5",
|
||||
|
@ -151,7 +152,7 @@
|
|||
"typescript": "2.5.3",
|
||||
"uuid": "3.1.0",
|
||||
"vhost": "3.0.2",
|
||||
"websocket": "1.0.24",
|
||||
"websocket": "1.0.25",
|
||||
"xev": "2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
398
src/api/bot/core.ts
Normal file
398
src/api/bot/core.ts
Normal file
|
@ -0,0 +1,398 @@
|
|||
import * as EventEmitter from 'events';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
|
||||
import User, { IUser, init as initUser } from '../models/user';
|
||||
|
||||
import getPostSummary from '../../common/get-post-summary';
|
||||
import getUserSummary from '../../common/get-user-summary';
|
||||
|
||||
import Othello, { ai as othelloAi } from '../../common/othello';
|
||||
|
||||
const hmm = [
|
||||
'?',
|
||||
'ふぅ~む...?',
|
||||
'ちょっと何言ってるかわからないです',
|
||||
'「ヘルプ」と言うと利用可能な操作が確認できますよ'
|
||||
];
|
||||
|
||||
/**
|
||||
* Botの頭脳
|
||||
*/
|
||||
export default class BotCore extends EventEmitter {
|
||||
public user: IUser = null;
|
||||
|
||||
private context: Context = null;
|
||||
|
||||
constructor(user?: IUser) {
|
||||
super();
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public clearContext() {
|
||||
this.setContext(null);
|
||||
}
|
||||
|
||||
public setContext(context: Context) {
|
||||
this.context = context;
|
||||
this.emit('updated');
|
||||
|
||||
if (context) {
|
||||
context.on('updated', () => {
|
||||
this.emit('updated');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public export() {
|
||||
return {
|
||||
user: this.user,
|
||||
context: this.context ? this.context.export() : null
|
||||
};
|
||||
}
|
||||
|
||||
protected _import(data) {
|
||||
this.user = data.user ? initUser(data.user) : null;
|
||||
this.setContext(data.context ? Context.import(this, data.context) : null);
|
||||
}
|
||||
|
||||
public static import(data) {
|
||||
const bot = new BotCore();
|
||||
bot._import(data);
|
||||
return bot;
|
||||
}
|
||||
|
||||
public async q(query: string): Promise<string | void> {
|
||||
if (this.context != null) {
|
||||
return await this.context.q(query);
|
||||
}
|
||||
|
||||
if (/^@[a-zA-Z0-9-]+$/.test(query)) {
|
||||
return await this.showUserCommand(query);
|
||||
}
|
||||
|
||||
switch (query) {
|
||||
case 'ping':
|
||||
return 'PONG';
|
||||
|
||||
case 'help':
|
||||
case 'ヘルプ':
|
||||
return '利用可能なコマンド一覧です:\n' +
|
||||
'help: これです\n' +
|
||||
'me: アカウント情報を見ます\n' +
|
||||
'login, signin: サインインします\n' +
|
||||
'logout, signout: サインアウトします\n' +
|
||||
'post: 投稿します\n' +
|
||||
'tl: タイムラインを見ます\n' +
|
||||
'@<ユーザー名>: ユーザーを表示します';
|
||||
|
||||
case 'me':
|
||||
return this.user ? `${this.user.name}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません';
|
||||
|
||||
case 'login':
|
||||
case 'signin':
|
||||
case 'ログイン':
|
||||
case 'サインイン':
|
||||
if (this.user != null) return '既にサインインしていますよ!';
|
||||
this.setContext(new SigninContext(this));
|
||||
return await this.context.greet();
|
||||
|
||||
case 'logout':
|
||||
case 'signout':
|
||||
case 'ログアウト':
|
||||
case 'サインアウト':
|
||||
if (this.user == null) return '今はサインインしてないですよ!';
|
||||
this.signout();
|
||||
return 'ご利用ありがとうございました <3';
|
||||
|
||||
case 'post':
|
||||
case '投稿':
|
||||
if (this.user == null) return 'まずサインインしてください。';
|
||||
this.setContext(new PostContext(this));
|
||||
return await this.context.greet();
|
||||
|
||||
case 'tl':
|
||||
case 'タイムライン':
|
||||
return await this.tlCommand();
|
||||
|
||||
case 'guessing-game':
|
||||
case '数当てゲーム':
|
||||
this.setContext(new GuessingGameContext(this));
|
||||
return await this.context.greet();
|
||||
|
||||
case 'othello':
|
||||
case 'オセロ':
|
||||
this.setContext(new OthelloContext(this));
|
||||
return await this.context.greet();
|
||||
|
||||
default:
|
||||
return hmm[Math.floor(Math.random() * hmm.length)];
|
||||
}
|
||||
}
|
||||
|
||||
public signin(user: IUser) {
|
||||
this.user = user;
|
||||
this.emit('signin', user);
|
||||
this.emit('updated');
|
||||
}
|
||||
|
||||
public signout() {
|
||||
const user = this.user;
|
||||
this.user = null;
|
||||
this.emit('signout', user);
|
||||
this.emit('updated');
|
||||
}
|
||||
|
||||
public async refreshUser() {
|
||||
this.user = await User.findOne({
|
||||
_id: this.user._id
|
||||
}, {
|
||||
fields: {
|
||||
data: false
|
||||
}
|
||||
});
|
||||
|
||||
this.emit('updated');
|
||||
}
|
||||
|
||||
public async tlCommand(): Promise<string | void> {
|
||||
if (this.user == null) return 'まずサインインしてください。';
|
||||
|
||||
const tl = await require('../endpoints/posts/timeline')({
|
||||
limit: 5
|
||||
}, this.user);
|
||||
|
||||
const text = tl
|
||||
.map(post => getPostSummary(post))
|
||||
.join('\n-----\n');
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public async showUserCommand(q: string): Promise<string | void> {
|
||||
try {
|
||||
const user = await require('../endpoints/users/show')({
|
||||
username: q.substr(1)
|
||||
}, this.user);
|
||||
|
||||
const text = getUserSummary(user);
|
||||
|
||||
return text;
|
||||
} catch (e) {
|
||||
return `問題が発生したようです...: ${e}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Context extends EventEmitter {
|
||||
protected bot: BotCore;
|
||||
|
||||
public abstract async greet(): Promise<string>;
|
||||
public abstract async q(query: string): Promise<string>;
|
||||
public abstract export(): any;
|
||||
|
||||
constructor(bot: BotCore) {
|
||||
super();
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
public static import(bot: BotCore, data: any) {
|
||||
if (data.type == 'guessing-game') return GuessingGameContext.import(bot, data.content);
|
||||
if (data.type == 'othello') return OthelloContext.import(bot, data.content);
|
||||
if (data.type == 'post') return PostContext.import(bot, data.content);
|
||||
if (data.type == 'signin') return SigninContext.import(bot, data.content);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SigninContext extends Context {
|
||||
private temporaryUser: IUser = null;
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return `${query}というユーザーは存在しませんでした... もう一度教えてください:`;
|
||||
} else {
|
||||
this.temporaryUser = user;
|
||||
this.emit('updated');
|
||||
return `パスワードを教えてください:`;
|
||||
}
|
||||
} else {
|
||||
// Compare password
|
||||
const same = bcrypt.compareSync(query, this.temporaryUser.password);
|
||||
|
||||
if (same) {
|
||||
this.bot.signin(this.temporaryUser);
|
||||
this.bot.clearContext();
|
||||
return `${this.temporaryUser.name}さん、おかえりなさい!`;
|
||||
} else {
|
||||
return `パスワードが違います... もう一度教えてください:`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public export() {
|
||||
return {
|
||||
type: 'signin',
|
||||
content: {
|
||||
temporaryUser: this.temporaryUser
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static import(bot: BotCore, data: any) {
|
||||
const context = new SigninContext(bot);
|
||||
context.temporaryUser = data.temporaryUser;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
class PostContext extends Context {
|
||||
public async greet(): Promise<string> {
|
||||
return '内容:';
|
||||
}
|
||||
|
||||
public async q(query: string): Promise<string> {
|
||||
await require('../endpoints/posts/create')({
|
||||
text: query
|
||||
}, this.bot.user);
|
||||
this.bot.clearContext();
|
||||
return '投稿しましたよ!';
|
||||
}
|
||||
|
||||
public export() {
|
||||
return {
|
||||
type: 'post'
|
||||
};
|
||||
}
|
||||
|
||||
public static import(bot: BotCore, data: any) {
|
||||
const context = new PostContext(bot);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
class GuessingGameContext extends Context {
|
||||
private secret: number;
|
||||
private history: number[] = [];
|
||||
|
||||
public async greet(): Promise<string> {
|
||||
this.secret = Math.floor(Math.random() * 100);
|
||||
this.emit('updated');
|
||||
return '0~100の秘密の数を当ててみてください:';
|
||||
}
|
||||
|
||||
public async q(query: string): Promise<string> {
|
||||
if (query == 'やめる') {
|
||||
this.bot.clearContext();
|
||||
return 'やめました。';
|
||||
}
|
||||
|
||||
const guess = parseInt(query, 10);
|
||||
|
||||
if (isNaN(guess)) {
|
||||
return '整数で推測してください。「やめる」と言うとゲームをやめます。';
|
||||
}
|
||||
|
||||
const firsttime = this.history.indexOf(guess) === -1;
|
||||
|
||||
this.history.push(guess);
|
||||
this.emit('updated');
|
||||
|
||||
if (this.secret < guess) {
|
||||
return firsttime ? `${guess}よりも小さいですね` : `もう一度言いますが${guess}より小さいですよ`;
|
||||
} else if (this.secret > guess) {
|
||||
return firsttime ? `${guess}よりも大きいですね` : `もう一度言いますが${guess}より大きいですよ`;
|
||||
} else {
|
||||
this.bot.clearContext();
|
||||
return `正解です🎉 (${this.history.length}回目で当てました)`;
|
||||
}
|
||||
}
|
||||
|
||||
public export() {
|
||||
return {
|
||||
type: 'guessing-game',
|
||||
content: {
|
||||
secret: this.secret,
|
||||
history: this.history
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static import(bot: BotCore, data: any) {
|
||||
const context = new GuessingGameContext(bot);
|
||||
context.secret = data.secret;
|
||||
context.history = data.history;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
class OthelloContext extends Context {
|
||||
private othello: Othello = null;
|
||||
|
||||
constructor(bot: BotCore) {
|
||||
super(bot);
|
||||
|
||||
this.othello = new Othello();
|
||||
}
|
||||
|
||||
public async greet(): Promise<string> {
|
||||
return this.othello.toPatternString('black');
|
||||
}
|
||||
|
||||
public async q(query: string): Promise<string> {
|
||||
if (query == 'やめる') {
|
||||
this.bot.clearContext();
|
||||
return 'オセロをやめました。';
|
||||
}
|
||||
|
||||
const n = parseInt(query, 10);
|
||||
|
||||
if (isNaN(n)) {
|
||||
return '番号で指定してください。「やめる」と言うとゲームをやめます。';
|
||||
}
|
||||
|
||||
this.othello.setByNumber('black', n);
|
||||
const s = this.othello.toString() + '\n\n...(AI)...\n\n';
|
||||
othelloAi('white', this.othello);
|
||||
if (this.othello.getPattern('black').length === 0) {
|
||||
this.bot.clearContext();
|
||||
const blackCount = this.othello.board.map(row => row.filter(s => s == 'black').length).reduce((a, b) => a + b);
|
||||
const whiteCount = this.othello.board.map(row => row.filter(s => s == 'white').length).reduce((a, b) => a + b);
|
||||
const winner = blackCount == whiteCount ? '引き分け' : blackCount > whiteCount ? '黒の勝ち' : '白の勝ち';
|
||||
return this.othello.toString() + `\n\n~終了~\n\n黒${blackCount}、白${whiteCount}で${winner}です。`;
|
||||
} else {
|
||||
this.emit('updated');
|
||||
return s + this.othello.toPatternString('black');
|
||||
}
|
||||
}
|
||||
|
||||
public export() {
|
||||
return {
|
||||
type: 'othello',
|
||||
content: {
|
||||
board: this.othello.board
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static import(bot: BotCore, data: any) {
|
||||
const context = new OthelloContext(bot);
|
||||
context.othello = new Othello();
|
||||
context.othello.board = data.board;
|
||||
return context;
|
||||
}
|
||||
}
|
234
src/api/bot/interfaces/line.ts
Normal file
234
src/api/bot/interfaces/line.ts
Normal file
|
@ -0,0 +1,234 @@
|
|||
import * as EventEmitter from 'events';
|
||||
import * as express from 'express';
|
||||
import * as request from 'request';
|
||||
import * as crypto from 'crypto';
|
||||
import User from '../../models/user';
|
||||
import config from '../../../conf';
|
||||
import BotCore from '../core';
|
||||
import _redis from '../../../db/redis';
|
||||
import prominence = require('prominence');
|
||||
import getPostSummary from '../../../common/get-post-summary';
|
||||
|
||||
const redis = prominence(_redis);
|
||||
|
||||
// SEE: https://developers.line.me/media/messaging-api/messages/sticker_list.pdf
|
||||
const stickers = [
|
||||
'297',
|
||||
'298',
|
||||
'299',
|
||||
'300',
|
||||
'301',
|
||||
'302',
|
||||
'303',
|
||||
'304',
|
||||
'305',
|
||||
'306',
|
||||
'307'
|
||||
];
|
||||
|
||||
class LineBot extends BotCore {
|
||||
private replyToken: string;
|
||||
|
||||
private reply(messages: any[]) {
|
||||
request.post({
|
||||
url: 'https://api.line.me/v2/bot/message/reply',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${config.line_bot.channel_access_token}`
|
||||
},
|
||||
json: {
|
||||
replyToken: this.replyToken,
|
||||
messages: messages
|
||||
}
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async react(ev: any): Promise<void> {
|
||||
this.replyToken = ev.replyToken;
|
||||
|
||||
switch (ev.type) {
|
||||
// メッセージ
|
||||
case 'message':
|
||||
switch (ev.message.type) {
|
||||
// テキスト
|
||||
case 'text':
|
||||
const res = await this.q(ev.message.text);
|
||||
if (res == null) return;
|
||||
// 返信
|
||||
this.reply([{
|
||||
type: 'text',
|
||||
text: res
|
||||
}]);
|
||||
break;
|
||||
|
||||
// スタンプ
|
||||
case 'sticker':
|
||||
// スタンプで返信
|
||||
this.reply([{
|
||||
type: 'sticker',
|
||||
packageId: '4',
|
||||
stickerId: stickers[Math.floor(Math.random() * stickers.length)]
|
||||
}]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// postback
|
||||
case 'postback':
|
||||
const data = ev.postback.data;
|
||||
const cmd = data.split('|')[0];
|
||||
const arg = data.split('|')[1];
|
||||
switch (cmd) {
|
||||
case 'showtl':
|
||||
this.showUserTimelinePostback(arg);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static import(data) {
|
||||
const bot = new LineBot();
|
||||
bot._import(data);
|
||||
return bot;
|
||||
}
|
||||
|
||||
public async showUserCommand(q: string) {
|
||||
const user = await require('../../endpoints/users/show')({
|
||||
username: q.substr(1)
|
||||
}, this.user);
|
||||
|
||||
const actions = [];
|
||||
|
||||
actions.push({
|
||||
type: 'postback',
|
||||
label: 'タイムラインを見る',
|
||||
data: `showtl|${user.id}`
|
||||
});
|
||||
|
||||
if (user.twitter) {
|
||||
actions.push({
|
||||
type: 'uri',
|
||||
label: 'Twitterアカウントを見る',
|
||||
uri: `https://twitter.com/${user.twitter.screen_name}`
|
||||
});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
type: 'uri',
|
||||
label: 'Webで見る',
|
||||
uri: `${config.url}/${user.username}`
|
||||
});
|
||||
|
||||
this.reply([{
|
||||
type: 'template',
|
||||
altText: await super.showUserCommand(q),
|
||||
template: {
|
||||
type: 'buttons',
|
||||
thumbnailImageUrl: `${user.avatar_url}?thumbnail&size=1024`,
|
||||
title: `${user.name} (@${user.username})`,
|
||||
text: user.description || '(no description)',
|
||||
actions: actions
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
public async showUserTimelinePostback(userId: string) {
|
||||
const tl = await require('../../endpoints/users/posts')({
|
||||
user_id: userId,
|
||||
limit: 5
|
||||
}, this.user);
|
||||
|
||||
const text = `${tl[0].user.name}さんのタイムラインはこちらです:\n\n` + tl
|
||||
.map(post => getPostSummary(post))
|
||||
.join('\n-----\n');
|
||||
|
||||
this.reply([{
|
||||
type: 'text',
|
||||
text: text
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async (app: express.Application) => {
|
||||
if (config.line_bot == null) return;
|
||||
|
||||
const handler = new EventEmitter();
|
||||
|
||||
handler.on('event', async (ev) => {
|
||||
|
||||
const sourceId = ev.source.userId;
|
||||
const sessionId = `line-bot-sessions:${sourceId}`;
|
||||
|
||||
const session = await redis.get(sessionId);
|
||||
let bot: LineBot;
|
||||
|
||||
if (session == null) {
|
||||
const user = await User.findOne({
|
||||
line: {
|
||||
user_id: sourceId
|
||||
}
|
||||
});
|
||||
|
||||
bot = new LineBot(user);
|
||||
|
||||
bot.on('signin', user => {
|
||||
User.update(user._id, {
|
||||
$set: {
|
||||
line: {
|
||||
user_id: sourceId
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bot.on('signout', user => {
|
||||
User.update(user._id, {
|
||||
$set: {
|
||||
line: {
|
||||
user_id: null
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
redis.set(sessionId, JSON.stringify(bot.export()));
|
||||
} else {
|
||||
bot = LineBot.import(JSON.parse(session));
|
||||
}
|
||||
|
||||
bot.on('updated', () => {
|
||||
redis.set(sessionId, JSON.stringify(bot.export()));
|
||||
});
|
||||
|
||||
if (session != null) bot.refreshUser();
|
||||
|
||||
bot.react(ev);
|
||||
});
|
||||
|
||||
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((req as any).rawBody);
|
||||
|
||||
const sig2 = hash.digest('base64');
|
||||
|
||||
// シグネチャ比較
|
||||
if (sig1 === sig2) {
|
||||
req.body.events.forEach(ev => {
|
||||
handler.emit('event', ev);
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
res.sendStatus(400);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -57,6 +57,9 @@ export type IUser = {
|
|||
user_id: string;
|
||||
screen_name: string;
|
||||
};
|
||||
line: {
|
||||
user_id: string;
|
||||
};
|
||||
description: string;
|
||||
profile: {
|
||||
location: string;
|
||||
|
@ -70,3 +73,11 @@ export type IUser = {
|
|||
is_suspended: boolean;
|
||||
keywords: string[];
|
||||
};
|
||||
|
||||
export function init(user): IUser {
|
||||
user._id = new mongo.ObjectID(user._id);
|
||||
user.avatar_id = new mongo.ObjectID(user.avatar_id);
|
||||
user.banner_id = new mongo.ObjectID(user.banner_id);
|
||||
user.pinned_post_id = new mongo.ObjectID(user.pinned_post_id);
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -19,7 +19,12 @@ app.disable('x-powered-by');
|
|||
app.set('etag', false);
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json({
|
||||
type: ['application/json', 'text/plain']
|
||||
type: ['application/json', 'text/plain'],
|
||||
verify: (req, res, buf, encoding) => {
|
||||
if (buf && buf.length) {
|
||||
(req as any).rawBody = buf.toString(encoding || 'utf8');
|
||||
}
|
||||
}
|
||||
}));
|
||||
app.use(cors({
|
||||
origin: true
|
||||
|
@ -54,4 +59,6 @@ app.use((req, res, next) => {
|
|||
require('./service/github')(app);
|
||||
require('./service/twitter')(app);
|
||||
|
||||
require('./bot/interfaces/line')(app);
|
||||
|
||||
module.exports = app;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
const summarize = post => {
|
||||
/**
|
||||
* 投稿を表す文字列を取得します。
|
||||
* @param {*} post 投稿
|
||||
*/
|
||||
const summarize = (post: any): string => {
|
||||
let summary = post.text ? post.text : '';
|
||||
|
||||
// メディアが添付されているとき
|
12
src/common/get-user-summary.ts
Normal file
12
src/common/get-user-summary.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { IUser } from '../api/models/user';
|
||||
|
||||
/**
|
||||
* ユーザーを表す文字列を取得します。
|
||||
* @param user ユーザー
|
||||
*/
|
||||
export default function(user: IUser): string {
|
||||
return `${user.name} (@${user.username})\n` +
|
||||
`${user.posts_count}投稿、${user.following_count}フォロー、${user.followers_count}フォロワー\n` +
|
||||
`場所: ${user.profile.location}、誕生日: ${user.profile.birthday}\n` +
|
||||
`「${user.description}」`;
|
||||
}
|
268
src/common/othello.ts
Normal file
268
src/common/othello.ts
Normal file
|
@ -0,0 +1,268 @@
|
|||
const BOARD_SIZE = 8;
|
||||
|
||||
export default class Othello {
|
||||
public board: Array<Array<'black' | 'white'>>;
|
||||
|
||||
/**
|
||||
* ゲームを初期化します
|
||||
*/
|
||||
constructor() {
|
||||
this.board = [
|
||||
[null, null, null, null, null, null, null, null],
|
||||
[null, null, null, null, null, null, null, null],
|
||||
[null, null, null, null, null, null, null, null],
|
||||
[null, null, null, 'black', 'white', null, null, null],
|
||||
[null, null, null, 'white', 'black', null, null, null],
|
||||
[null, null, null, null, null, null, null, null],
|
||||
[null, null, null, null, null, null, null, null],
|
||||
[null, null, null, null, null, null, null, null]
|
||||
];
|
||||
}
|
||||
|
||||
public setByNumber(color, n) {
|
||||
const ps = this.getPattern(color);
|
||||
this.set(color, ps[n][0], ps[n][1]);
|
||||
}
|
||||
|
||||
private write(color, x, y) {
|
||||
this.board[y][x] = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 石を配置します
|
||||
*/
|
||||
public set(color, x, y) {
|
||||
this.write(color, x, y);
|
||||
|
||||
const reverses = this.getReverse(color, x, y);
|
||||
|
||||
reverses.forEach(r => {
|
||||
switch (r[0]) {
|
||||
case 0: // 上
|
||||
for (let c = 0, _y = y - 1; c < r[1]; c++, _y--) {
|
||||
this.write(color, x, _y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // 右上
|
||||
for (let c = 0, i = 1; c < r[1]; c++, i++) {
|
||||
this.write(color, x + i, y - i);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // 右
|
||||
for (let c = 0, _x = x + 1; c < r[1]; c++, _x++) {
|
||||
this.write(color, _x, y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // 右下
|
||||
for (let c = 0, i = 1; c < r[1]; c++, i++) {
|
||||
this.write(color, x + i, y + i);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // 下
|
||||
for (let c = 0, _y = y + 1; c < r[1]; c++, _y++) {
|
||||
this.write(color, x, _y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // 左下
|
||||
for (let c = 0, i = 1; c < r[1]; c++, i++) {
|
||||
this.write(color, x - i, y + i);
|
||||
}
|
||||
break;
|
||||
|
||||
case 6: // 左
|
||||
for (let c = 0, _x = x - 1; c < r[1]; c++, _x--) {
|
||||
this.write(color, _x, y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // 左上
|
||||
for (let c = 0, i = 1; c < r[1]; c++, i++) {
|
||||
this.write(color, x - i, y - i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打つことができる場所を取得します
|
||||
*/
|
||||
public getPattern(myColor): number[][] {
|
||||
const result = [];
|
||||
this.board.forEach((stones, y) => stones.forEach((stone, x) => {
|
||||
if (stone != null) return;
|
||||
if (this.canReverse(myColor, x, y)) result.push([x, y]);
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定の位置に石を打つことができるかどうか(相手の石を1つでも反転させられるか)を取得します
|
||||
*/
|
||||
public canReverse(myColor, targetx, targety): boolean {
|
||||
return this.getReverse(myColor, targetx, targety) !== null;
|
||||
}
|
||||
|
||||
private getReverse(myColor, targetx, targety): number[] {
|
||||
const opponentColor = myColor == 'black' ? 'white' : 'black';
|
||||
|
||||
const createIterater = () => {
|
||||
let opponentStoneFound = false;
|
||||
let breaked = false;
|
||||
return (x, y): any => {
|
||||
if (breaked) {
|
||||
return;
|
||||
} else if (this.board[y][x] == myColor && opponentStoneFound) {
|
||||
return true;
|
||||
} else if (this.board[y][x] == myColor && !opponentStoneFound) {
|
||||
breaked = true;
|
||||
} else if (this.board[y][x] == opponentColor) {
|
||||
opponentStoneFound = true;
|
||||
} else {
|
||||
breaked = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const res = [];
|
||||
|
||||
let iterate;
|
||||
|
||||
// 上
|
||||
iterate = createIterater();
|
||||
for (let c = 0, y = targety - 1; y >= 0; c++, y--) {
|
||||
if (iterate(targetx, y)) {
|
||||
res.push([0, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 右上
|
||||
iterate = createIterater();
|
||||
for (let c = 0, i = 1; i <= Math.min(BOARD_SIZE - targetx, targety); c++, i++) {
|
||||
if (iterate(targetx + i, targety - i)) {
|
||||
res.push([1, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 右
|
||||
iterate = createIterater();
|
||||
for (let c = 0, x = targetx + 1; x < BOARD_SIZE; c++, x++) {
|
||||
if (iterate(x, targety)) {
|
||||
res.push([2, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 右下
|
||||
iterate = createIterater();
|
||||
for (let c = 0, i = 1; i < Math.min(BOARD_SIZE - targetx, BOARD_SIZE - targety); c++, i++) {
|
||||
if (iterate(targetx + i, targety + i)) {
|
||||
res.push([3, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 下
|
||||
iterate = createIterater();
|
||||
for (let c = 0, y = targety + 1; y < BOARD_SIZE; c++, y++) {
|
||||
if (iterate(targetx, y)) {
|
||||
res.push([4, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 左下
|
||||
iterate = createIterater();
|
||||
for (let c = 0, i = 1; i < Math.min(targetx, BOARD_SIZE - targety); c++, i++) {
|
||||
if (iterate(targetx - i, targety + i)) {
|
||||
res.push([5, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 左
|
||||
iterate = createIterater();
|
||||
for (let c = 0, x = targetx - 1; x >= 0; c++, x--) {
|
||||
if (iterate(x, targety)) {
|
||||
res.push([6, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 左上
|
||||
iterate = createIterater();
|
||||
for (let c = 0, i = 1; i <= Math.min(targetx, targety); c++, i++) {
|
||||
if (iterate(targetx - i, targety - i)) {
|
||||
res.push([7, c]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res.length === 0 ? null : res;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
//return this.board.map(row => row.map(state => state === 'black' ? '●' : state === 'white' ? '○' : '┼').join('')).join('\n');
|
||||
return this.board.map(row => row.map(state => state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : '🔹').join('')).join('\n');
|
||||
}
|
||||
|
||||
public toPatternString(color): string {
|
||||
//const num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
const num = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🍍'];
|
||||
|
||||
const pattern = this.getPattern(color);
|
||||
|
||||
return this.board.map((row, y) => row.map((state, x) => {
|
||||
const i = pattern.findIndex(p => p[0] == x && p[1] == y);
|
||||
//return state === 'black' ? '●' : state === 'white' ? '○' : i != -1 ? num[i] : '┼';
|
||||
return state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : i != -1 ? num[i] : '🔹';
|
||||
}).join('')).join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
export function ai(color: string, othello: Othello) {
|
||||
const opponentColor = color == 'black' ? 'white' : 'black';
|
||||
|
||||
function think() {
|
||||
// 打てる場所を取得
|
||||
const ps = othello.getPattern(color);
|
||||
|
||||
if (ps.length > 0) { // 打てる場所がある場合
|
||||
// 角を取得
|
||||
const corners = ps.filter(p =>
|
||||
// 左上
|
||||
(p[0] == 0 && p[1] == 0) ||
|
||||
// 右上
|
||||
(p[0] == (BOARD_SIZE - 1) && p[1] == 0) ||
|
||||
// 右下
|
||||
(p[0] == (BOARD_SIZE - 1) && p[1] == (BOARD_SIZE - 1)) ||
|
||||
// 左下
|
||||
(p[0] == 0 && p[1] == (BOARD_SIZE - 1))
|
||||
);
|
||||
|
||||
if (corners.length > 0) { // どこかしらの角に打てる場合
|
||||
// 打てる角からランダムに選択して打つ
|
||||
const p = corners[Math.floor(Math.random() * corners.length)];
|
||||
othello.set(color, p[0], p[1]);
|
||||
} else { // 打てる角がない場合
|
||||
// 打てる場所からランダムに選択して打つ
|
||||
const p = ps[Math.floor(Math.random() * ps.length)];
|
||||
othello.set(color, p[0], p[1]);
|
||||
}
|
||||
|
||||
// 相手の打つ場所がない場合続けてAIのターン
|
||||
if (othello.getPattern(opponentColor).length === 0) {
|
||||
think();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
think();
|
||||
}
|
|
@ -68,6 +68,10 @@ type Source = {
|
|||
hook_secret: string;
|
||||
username: string;
|
||||
};
|
||||
line_bot?: {
|
||||
channel_secret: string;
|
||||
channel_access_token: string;
|
||||
};
|
||||
analysis?: {
|
||||
mecab_command?: string;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmitOnError": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": true,
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<mk-error>
|
||||
<img src="/assets/error.jpg" alt=""/>
|
||||
<img src="data:image/jpeg;base64,%base64:/assets/error.jpg%" alt=""/>
|
||||
<h1>%i18n:common.tags.mk-error.title%</h1>
|
||||
<p class="text">%i18n:common.tags.mk-error.description%</p>
|
||||
<p class="text">{
|
||||
'%i18n:common.tags.mk-error.description%'.substr(0, '%i18n:common.tags.mk-error.description%'.indexOf('{'))
|
||||
}<a onclick={ reload }>{
|
||||
'%i18n:common.tags.mk-error.description%'.match(/\{(.+?)\}/)[1]
|
||||
}</a>{
|
||||
'%i18n:common.tags.mk-error.description%'.substr('%i18n:common.tags.mk-error.description%'.indexOf('}') + 1)
|
||||
}</p>
|
||||
<p class="thanks">%i18n:common.tags.mk-error.thanks%</p>
|
||||
<style>
|
||||
:scope
|
||||
|
@ -53,5 +59,9 @@
|
|||
document.title = 'Oops!';
|
||||
document.documentElement.style.background = '#f8f8f8';
|
||||
});
|
||||
|
||||
this.reload = () => {
|
||||
location.reload();
|
||||
};
|
||||
</script>
|
||||
</mk-error>
|
||||
|
|
|
@ -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.ts';
|
||||
|
||||
/**
|
||||
* init
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<mk-version-home-widget>
|
||||
<p>ver{ version }</p>
|
||||
<p>ver { version }</p>
|
||||
<style>
|
||||
:scope
|
||||
display block
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import getPostSummary from '../../common/scripts/get-post-summary';
|
||||
import getPostSummary from '../../../../common/get-post-summary.ts';
|
||||
this.getPostSummary = getPostSummary;
|
||||
|
||||
this.mixin('i');
|
||||
|
|
|
@ -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.ts';
|
||||
|
||||
this.mixin('i');
|
||||
this.mixin('api');
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import getPostSummary from '../../common/scripts/get-post-summary';
|
||||
import getPostSummary from '../../../../common/get-post-summary.ts';
|
||||
this.getPostSummary = getPostSummary;
|
||||
this.notification = this.opts.notification;
|
||||
</script>
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import getPostSummary from '../../common/scripts/get-post-summary';
|
||||
import getPostSummary from '../../../../common/get-post-summary.ts';
|
||||
this.getPostSummary = getPostSummary;
|
||||
this.notification = this.opts.notification;
|
||||
</script>
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import getPostSummary from '../../common/scripts/get-post-summary';
|
||||
import getPostSummary from '../../../../common/get-post-summary.ts';
|
||||
this.getPostSummary = getPostSummary;
|
||||
|
||||
this.mixin('api');
|
||||
|
|
|
@ -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.ts';
|
||||
import openPostForm from '../../scripts/open-post-form';
|
||||
|
||||
this.mixin('i');
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<ul>
|
||||
<li><a onclick={ signout }><i class="fa fa-power-off"></i>%i18n:mobile.tags.mk-settings-page.signout%</a></li>
|
||||
</ul>
|
||||
<p><small>ver { version }</small></p>
|
||||
<style>
|
||||
:scope
|
||||
display block
|
||||
|
@ -96,5 +97,7 @@
|
|||
this.signout = signout;
|
||||
|
||||
this.mixin('i');
|
||||
|
||||
this.version = VERSION;
|
||||
</script>
|
||||
</mk-settings>
|
||||
|
|
|
@ -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.ts';
|
||||
import openPostForm from '../scripts/open-post-form';
|
||||
|
||||
this.mixin('api');
|
||||
|
|
|
@ -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.ts';
|
||||
import openPostForm from '../scripts/open-post-form';
|
||||
|
||||
this.mixin('api');
|
||||
|
|
|
@ -428,7 +428,7 @@
|
|||
|
||||
</style>
|
||||
<script>
|
||||
import summary from '../../common/scripts/get-post-summary';
|
||||
import summary from '../../../../common/get-post-summary.ts';
|
||||
|
||||
this.post = this.opts.post;
|
||||
this.text = summary(this.post);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmitOnError": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": true,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"ordered-imports": [false],
|
||||
"arrow-parens": false,
|
||||
"object-literal-shorthand": false,
|
||||
"object-literal-key-quotes": false,
|
||||
"triple-equals": [false],
|
||||
"no-shadowed-variable": false,
|
||||
"no-string-literal": false,
|
||||
|
@ -23,6 +24,7 @@
|
|||
"comment-format": [false],
|
||||
"interface-over-type-literal": false,
|
||||
"max-line-length": [false],
|
||||
"max-classes-per-file": false,
|
||||
"member-ordering": [false],
|
||||
"ban-types": [
|
||||
"Object"
|
||||
|
|
19
webpack/module/rules/base64.ts
Normal file
19
webpack/module/rules/base64.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Replace base64 symbols
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
const StringReplacePlugin = require('string-replace-webpack-plugin');
|
||||
|
||||
export default () => ({
|
||||
enforce: 'pre',
|
||||
test: /\.(tag|js)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: StringReplacePlugin.replace({
|
||||
replacements: [{
|
||||
pattern: /%base64:(.+?)%/g, replacement: (_, key) => {
|
||||
return fs.readFileSync(__dirname + '/../../../src/web/' + key, 'base64');
|
||||
}
|
||||
}]
|
||||
})
|
||||
});
|
|
@ -1,11 +1,15 @@
|
|||
import i18n from './i18n';
|
||||
import base64 from './base64';
|
||||
import themeColor from './theme-color';
|
||||
import tag from './tag';
|
||||
import stylus from './stylus';
|
||||
import typescript from './typescript';
|
||||
|
||||
export default (lang, locale) => [
|
||||
i18n(lang, locale),
|
||||
base64(),
|
||||
themeColor(),
|
||||
tag(),
|
||||
stylus()
|
||||
stylus(),
|
||||
typescript()
|
||||
];
|
||||
|
|
8
webpack/module/rules/typescript.ts
Normal file
8
webpack/module/rules/typescript.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* TypeScript
|
||||
*/
|
||||
|
||||
export default () => ({
|
||||
test: /\.ts$/,
|
||||
use: 'awesome-typescript-loader'
|
||||
});
|
|
@ -2,13 +2,11 @@ const StringReplacePlugin = require('string-replace-webpack-plugin');
|
|||
|
||||
import constant from './const';
|
||||
import hoist from './hoist';
|
||||
//import minify from './minify';
|
||||
import minify from './minify';
|
||||
import banner from './banner';
|
||||
|
||||
/*
|
||||
const env = process.env.NODE_ENV;
|
||||
const isProduction = env === 'production';
|
||||
*/
|
||||
|
||||
export default version => {
|
||||
const plugins = [
|
||||
|
@ -16,11 +14,11 @@ export default version => {
|
|||
new StringReplacePlugin(),
|
||||
hoist()
|
||||
];
|
||||
/*
|
||||
|
||||
if (isProduction) {
|
||||
plugins.push(minify());
|
||||
}
|
||||
*/
|
||||
|
||||
plugins.push(banner(version));
|
||||
|
||||
return plugins;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
const UglifyEsPlugin = require('uglify-es-webpack-plugin');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
export default () => new UglifyEsPlugin();
|
||||
export default () => new UglifyJsPlugin();
|
||||
|
|
Loading…
Reference in a new issue