diff --git a/package-lock.json b/package-lock.json index 939657b..dfde776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,6 +3,14 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@types/chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==", + "requires": { + "chalk": "*" + } + }, "@types/events": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", @@ -142,9 +150,9 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -157,17 +165,17 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "1.1.1" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { "version": "1.0.6", @@ -561,9 +569,9 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } diff --git a/package.json b/package.json index 261e7fc..ef9b401 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,14 @@ "build": "tsc" }, "dependencies": { + "@types/chalk": "2.2.0", "@types/lokijs": "1.5.2", "@types/node": "10.0.5", "@types/promise-retry": "1.1.2", "@types/seedrandom": "2.4.27", "@types/ws": "6.0.1", "autobind-decorator": "2.1.0", + "chalk": "2.4.2", "lokijs": "1.5.5", "mecab-async": "0.1.2", "misskey-reversi": "0.0.5", diff --git a/src/ai.ts b/src/ai.ts index 2fc308b..6188d9d 100644 --- a/src/ai.ts +++ b/src/ai.ts @@ -1,22 +1,38 @@ // AI CORE +import autobind from 'autobind-decorator'; import * as loki from 'lokijs'; import * as request from 'request-promise-native'; +import chalk from 'chalk'; import config from './config'; -import IModule from './module'; +import Module from './module'; import MessageLike from './message-like'; import { FriendDoc } from './friend'; import { User } from './misskey/user'; import getCollection from './utils/get-collection'; import Stream from './stream'; +type OnMentionHandler = (msg: MessageLike) => boolean | HandlerResult; +type OnContextReplyHandler = (msg: MessageLike, data?: any) => void | HandlerResult; + +export type HandlerResult = { + reaction: string; +}; + +export type InstallerResult = { + onMention?: OnMentionHandler; + onContextReply?: OnContextReplyHandler; +}; + /** * 藍 */ export default class 藍 { public account: User; public connection: Stream; - private modules: IModule[] = []; + public modules: Module[] = []; + private onMentionHandlers: OnMentionHandler[] = []; + private onContextReplyHandlers: { [moduleName: string]: OnContextReplyHandler } = {}; public db: loki; private contexts: loki.Collection<{ @@ -30,19 +46,30 @@ export default class 藍 { public friends: loki.Collection; - constructor(account: User, modules: IModule[]) { + constructor(account: User, ready?: Function) { this.account = account; - this.modules = modules; this.db = new loki('memory.json', { autoload: true, autosave: true, autosaveInterval: 1000, - autoloadCallback: this.init + autoloadCallback: err => { + if (err) { + this.log(chalk.red(`Failed to load DB: ${err}`)); + } else { + if (ready) ready(); + } + } }); } - private init = () => { + @autobind + public log(msg: string) { + console.log(`[AiOS]: ${msg}`); + } + + @autobind + public run() { //#region Init DB this.contexts = getCollection(this.db, 'contexts', { indices: ['key'] @@ -62,7 +89,7 @@ export default class 藍 { // メンションされたとき mainStream.on('mention', data => { if (data.userId == this.account.id) return; // 自分は弾く - if (data.text.startsWith('@' + this.account.username)) { + if (data.text && data.text.startsWith('@' + this.account.username)) { this.onMention(new MessageLike(this, data, false)); } }); @@ -81,11 +108,21 @@ export default class 藍 { //#endregion // Install modules - this.modules.forEach(m => m.install(this)); + this.modules.forEach(m => { + this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`); + const res = m.install(); + if (res != null) { + if (res.onMention) this.onMentionHandlers.push(res.onMention); + if (res.onContextReply) this.onContextReplyHandlers[m.name] = res.onContextReply; + } + }); + + this.log(chalk.green.bold('Ai am now running!')); } - private onMention = (msg: MessageLike) => { - console.log(`mention received: ${msg.id}`); + @autobind + private onMention(msg: MessageLike) { + this.log(`mention received: ${msg.id}`); const context = !msg.isMessage && msg.replyId == null ? null : this.contexts.findOne(msg.isMessage ? { isMessage: true, @@ -98,17 +135,17 @@ export default class 藍 { let reaction = 'love'; if (context != null) { - const module = this.modules.find(m => m.name == context.module); - const res = module.onReplyThisModule(msg, context.data); + const handler = this.onContextReplyHandlers[context.module]; + const res = handler(msg, context.data); if (res != null && typeof res === 'object') { reaction = res.reaction; } } else { - let res: ReturnType; + let res: boolean | HandlerResult; - this.modules.filter(m => m.hasOwnProperty('onMention')).some(m => { - res = m.onMention(msg); + this.onMentionHandlers.some(handler => { + res = handler(msg); return res === true || typeof res === 'object'; }); @@ -135,18 +172,21 @@ export default class 藍 { }, 1000); } - public post = async (param: any) => { + @autobind + public async post(param: any) { const res = await this.api('notes/create', param); return res.createdNote; } - public sendMessage = (userId: any, param: any) => { + @autobind + public sendMessage(userId: any, param: any) { return this.api('messaging/messages/create', Object.assign({ userId: userId, }, param)); } - public api = (endpoint: string, param?: any) => { + @autobind + public api(endpoint: string, param?: any) { return request.post(`${config.apiUrl}/${endpoint}`, { json: Object.assign({ i: config.i @@ -154,7 +194,8 @@ export default class 藍 { }); }; - public subscribeReply = (module: IModule, key: string, isMessage: boolean, id: string, data?: any) => { + @autobind + public subscribeReply(module: Module, key: string, isMessage: boolean, id: string, data?: any) { this.contexts.insertOne(isMessage ? { isMessage: true, userId: id, @@ -170,7 +211,8 @@ export default class 藍 { }); } - public unsubscribeReply = (module: IModule, key: string) => { + @autobind + public unsubscribeReply(module: Module, key: string) { this.contexts.findAndRemove({ key: key, module: module.name diff --git a/src/index.ts b/src/index.ts index 4d37b38..1fbaabf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,42 +16,48 @@ import ServerModule from './modules/server'; import FollowModule from './modules/follow'; import ValentineModule from './modules/valentine'; +import chalk from 'chalk'; import * as request from 'request-promise-native'; -import IModule from './module'; const promiseRetry = require('promise-retry'); -console.log('--- starting ai... ---'); +function log(msg: string): void { + console.log(`[Boot]: ${msg}`); +} + +log(chalk.bold('Ai v1.0')); promiseRetry(retry => { + log(`Account fetching... >>> ${config.host}`); return request.post(`${config.apiUrl}/i`, { json: { i: config.i } }).catch(retry); +}, { + retries: 3 }).then(account => { - console.log(`account fetched: @${account.username}`); + log(chalk.green(`Account fetched successfully: @${account.username}`)); - const modules: IModule[] = [ - new EmojiModule(), - new FortuneModule(), - new GuessingGameModule(), - new ReversiModule(), - new TimerModule(), - new DiceModule(), - new CoreModule(), - new PingModule(), - new WelcomeModule(), - new ServerModule(), - new FollowModule(), - new BirthdayModule(), - new ValentineModule(), - ]; + log('Starting AiOS...'); - if (config.keywordEnabled) modules.push(new KeywordModule()); + const ai = new 藍(account); - new 藍(account, modules); + new EmojiModule(ai); + new FortuneModule(ai); + new GuessingGameModule(ai); + new ReversiModule(ai); + new TimerModule(ai); + new DiceModule(ai); + new CoreModule(ai); + new PingModule(ai); + new WelcomeModule(ai); + new ServerModule(ai); + new FollowModule(ai); + new BirthdayModule(ai); + new ValentineModule(ai); + if (config.keywordEnabled) new KeywordModule(ai); - console.log('--- ai started! ---'); + ai.run(); }).catch(e => { - console.error('failed to fetch account', e); + log(chalk.red('Failed to fetch the account')); }); diff --git a/src/message-like.ts b/src/message-like.ts index 59a7f94..4751f3b 100644 --- a/src/message-like.ts +++ b/src/message-like.ts @@ -50,7 +50,7 @@ export default class MessageLike { public reply = async (text: string, cw?: string) => { if (text == null) return; - console.log(`sending reply of ${this.id} ...`); + this.ai.log(`sending reply of ${this.id} ...`); await delay(2000); diff --git a/src/module.ts b/src/module.ts index 0fdb025..50c4e04 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,13 +1,30 @@ -import 藍 from './ai'; -import MessageLike from './message-like'; +import autobind from 'autobind-decorator'; +import 藍, { InstallerResult } from './ai'; -export default interface IModule { - name: string; - install?: (ai: 藍) => void; - onMention?: (msg: MessageLike) => boolean | Result; - onReplyThisModule?: (msg: MessageLike, data?: any) => void | Result; +export default abstract class Module { + public abstract name: string; + + protected ai: 藍; + + constructor(ai: 藍) { + this.ai = ai; + this.ai.modules.push(this); + } + + public abstract install(): InstallerResult; + + @autobind + protected log(msg: string) { + this.ai.log(`[module ${this.name}]: ${msg}`); + } + + @autobind + public subscribeReply(key: string, isMessage: boolean, id: string, data?: any) { + this.ai.subscribeReply(this, key, isMessage, id, data); + } + + @autobind + public unsubscribeReply(key: string) { + this.ai.unsubscribeReply(this, key); + } } - -export type Result = { - reaction: string; -}; diff --git a/src/modules/birthday/index.ts b/src/modules/birthday/index.ts index 31bc921..1ceb0cd 100644 --- a/src/modules/birthday/index.ts +++ b/src/modules/birthday/index.ts @@ -1,5 +1,5 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import Friend from '../../friend'; import serifs from '../../serifs'; @@ -7,22 +7,22 @@ function zeroPadding(num: number, length: number): string { return ('0000000000' + num).slice(-length); } -export default class BirthdayModule implements IModule { +export default class BirthdayModule extends Module { public readonly name = 'birthday'; - private ai: 藍; - - public install = (ai: 藍) => { - this.ai = ai; - + @autobind + public install() { this.crawleBirthday(); setInterval(this.crawleBirthday, 1000 * 60 * 3); + + return {}; } /** * 誕生日のユーザーがいないかチェック(いたら祝う) */ - private crawleBirthday = () => { + @autobind + private crawleBirthday() { const now = new Date(); const m = now.getMonth(); const d = now.getDate(); diff --git a/src/modules/core/index.ts b/src/modules/core/index.ts index 5a0bd07..feda1c6 100644 --- a/src/modules/core/index.ts +++ b/src/modules/core/index.ts @@ -1,5 +1,6 @@ -import 藍 from '../../ai'; -import IModule, { Result } from '../../module'; +import autobind from 'autobind-decorator'; +import { HandlerResult } from '../../ai'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs, { getSerif } from '../../serifs'; import getDate from '../../utils/get-date'; @@ -8,15 +9,19 @@ const titles = ['さん', 'くん', '君', 'ちゃん', '様', '先生']; const invalidChars = ['@', '#', '*', ':', '(', '[', ' ', ' ']; -export default class CoreModule implements IModule { +export default class CoreModule extends Module { public readonly name = 'core'; - private ai: 藍; - public install = (ai: 藍) => { - this.ai = ai; + @autobind + public install() { + return { + onMention: this.onMention, + onContextReply: this.onContextReply + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (!msg.text) return false; return ( @@ -34,7 +39,8 @@ export default class CoreModule implements IModule { ); } - private setName = (msg: MessageLike): boolean => { + @autobind + private setName(msg: MessageLike): boolean { if (!msg.text) return false; if (!msg.text.includes('って呼んで')) return false; if (msg.text.startsWith('って呼んで')) return false; @@ -66,7 +72,7 @@ export default class CoreModule implements IModule { msg.reply(serifs.core.setNameOk(name)); } else { msg.reply(serifs.core.san).then(reply => { - this.ai.subscribeReply(this, msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id, { + this.subscribeReply(msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id, { name: name }); }); @@ -75,7 +81,8 @@ export default class CoreModule implements IModule { return true; } - private greet = (msg: MessageLike): boolean => { + @autobind + private greet(msg: MessageLike): boolean { if (msg.text == null) return false; const incLove = () => { @@ -143,7 +150,8 @@ export default class CoreModule implements IModule { return false; } - private nadenade = (msg: MessageLike): boolean => { + @autobind + private nadenade(msg: MessageLike): boolean { if (!msg.includes(['なでなで'])) return false; // メッセージのみ @@ -177,7 +185,8 @@ export default class CoreModule implements IModule { return true; } - private kawaii = (msg: MessageLike): boolean => { + @autobind + private kawaii(msg: MessageLike): boolean { if (!msg.includes(['かわいい', '可愛い'])) return false; // メッセージのみ @@ -191,7 +200,8 @@ export default class CoreModule implements IModule { return true; } - private suki = (msg: MessageLike): boolean => { + @autobind + private suki(msg: MessageLike): boolean { if (!msg.or(['好き', 'すき'])) return false; // メッセージのみ @@ -205,7 +215,8 @@ export default class CoreModule implements IModule { return true; } - private hug = (msg: MessageLike): boolean => { + @autobind + private hug(msg: MessageLike): boolean { if (!msg.or(['ぎゅ', 'むぎゅ', /^はぐ(し(て|よ|よう)?)?$/])) return false; // メッセージのみ @@ -238,7 +249,8 @@ export default class CoreModule implements IModule { return true; } - private humu = (msg: MessageLike): boolean => { + @autobind + private humu(msg: MessageLike): boolean { if (!msg.includes(['踏んで'])) return false; // メッセージのみ @@ -252,7 +264,8 @@ export default class CoreModule implements IModule { return true; } - private batou = (msg: MessageLike): boolean => { + @autobind + private batou(msg: MessageLike): boolean { if (!msg.includes(['罵倒して', '罵って'])) return false; // メッセージのみ @@ -266,7 +279,8 @@ export default class CoreModule implements IModule { return true; } - private ponkotu = (msg: MessageLike): boolean | Result => { + @autobind + private ponkotu(msg: MessageLike): boolean | HandlerResult { if (!msg.includes(['ぽんこつ'])) return false; msg.friend.decLove(); @@ -276,7 +290,8 @@ export default class CoreModule implements IModule { }; } - private rmrf = (msg: MessageLike): boolean | Result => { + @autobind + private rmrf(msg: MessageLike): boolean | HandlerResult { if (!msg.includes(['rm -rf'])) return false; msg.friend.decLove(); @@ -286,7 +301,8 @@ export default class CoreModule implements IModule { }; } - private shutdown = (msg: MessageLike): boolean | Result => { + @autobind + private shutdown(msg: MessageLike): boolean | HandlerResult { if (!msg.includes(['shutdown'])) return false; msg.reply(serifs.core.shutdown); @@ -296,12 +312,13 @@ export default class CoreModule implements IModule { }; } - public onReplyThisModule = (msg: MessageLike, data: any) => { + @autobind + private onContextReply(msg: MessageLike, data: any) { if (msg.text == null) return; const done = () => { msg.reply(serifs.core.setNameOk(msg.friend.name)); - this.ai.unsubscribeReply(this, msg.userId); + this.unsubscribeReply(msg.userId); }; if (msg.text.includes('はい')) { @@ -312,7 +329,7 @@ export default class CoreModule implements IModule { done(); } else { msg.reply(serifs.core.yesOrNo).then(reply => { - this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id, data); + this.subscribeReply(msg.userId, msg.isMessage, reply.id, data); }); } } diff --git a/src/modules/dice/index.ts b/src/modules/dice/index.ts index e3753f8..d3904b0 100644 --- a/src/modules/dice/index.ts +++ b/src/modules/dice/index.ts @@ -1,17 +1,20 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; -export default class DiceModule implements IModule { +export default class DiceModule extends Module { public readonly name = 'dice'; - private ai: 藍; - public install = (ai: 藍) => { - this.ai = ai; + @autobind + public install() { + return { + onMention: this.onMention + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.text == null) return false; const query = msg.text.match(/([0-9]+)[dD]([0-9]+)/); diff --git a/src/modules/emoji/index.ts b/src/modules/emoji/index.ts index e3903e7..ec43924 100644 --- a/src/modules/emoji/index.ts +++ b/src/modules/emoji/index.ts @@ -1,5 +1,5 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; @@ -126,12 +126,18 @@ const faces = [ '👽' ] -export default class EmojiModule implements IModule { +export default class EmojiModule extends Module { public readonly name = 'emoji'; - public install = (ai: 藍) => { } + @autobind + public install() { + return { + onMention: this.onMention + }; + } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.includes(['顔文字', '絵文字', 'emoji', '福笑い'])) { const hand = hands[Math.floor(Math.random() * hands.length)]; const face = faces[Math.floor(Math.random() * faces.length)]; diff --git a/src/modules/follow/index.ts b/src/modules/follow/index.ts index 6719d91..b7c8b67 100644 --- a/src/modules/follow/index.ts +++ b/src/modules/follow/index.ts @@ -1,16 +1,19 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; -export default class FollowModule implements IModule { +export default class FollowModule extends Module { public readonly name = 'follow'; - private ai: 藍; - public install = (ai: 藍) => { - this.ai = ai; + @autobind + public install() { + return { + onMention: this.onMention + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.text && msg.includes(['フォロー', 'フォロバ', 'follow me'])) { if (!msg.user.isFollowing) { this.ai.api('following/create', { diff --git a/src/modules/fortune/index.ts b/src/modules/fortune/index.ts index 20191c4..3d25728 100644 --- a/src/modules/fortune/index.ts +++ b/src/modules/fortune/index.ts @@ -1,16 +1,22 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; import * as seedrandom from 'seedrandom'; import { blessing, itemPrefixes, items } from './vocabulary'; -export default class FortuneModule implements IModule { +export default class FortuneModule extends Module { public readonly name = 'fortune'; - public install = (ai: 藍) => { } + @autobind + public install() { + return { + onMention: this.onMention + }; + } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.includes(['占', 'うらな', '運勢', 'おみくじ'])) { const date = new Date(); const seed = `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}@${msg.userId}`; diff --git a/src/modules/guessing-game/index.ts b/src/modules/guessing-game/index.ts index a96125b..bade03c 100644 --- a/src/modules/guessing-game/index.ts +++ b/src/modules/guessing-game/index.ts @@ -1,13 +1,12 @@ +import autobind from 'autobind-decorator'; import * as loki from 'lokijs'; -import 藍 from '../../ai'; -import IModule from '../../module'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; import getCollection from '../../utils/get-collection'; -export default class GuessingGameModule implements IModule { +export default class GuessingGameModule extends Module { public readonly name = 'guessingGame'; - private ai: 藍; private guesses: loki.Collection<{ userId: string; secret: number; @@ -17,17 +16,22 @@ export default class GuessingGameModule implements IModule { endedAt: number; }>; - public install = (ai: 藍) => { - this.ai = ai; - + @autobind + public install() { //#region Init DB this.guesses = getCollection(this.ai.db, 'guessingGame', { indices: ['userId'] }); //#endregion + + return { + onMention: this.onMention, + onContextReply: this.onContextReply + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.includes(['数当て', '数あて'])) { const exist = this.guesses.findOne({ userId: msg.userId, @@ -56,7 +60,7 @@ export default class GuessingGameModule implements IModule { }); msg.reply(serifs.guessingGame.started).then(reply => { - this.ai.subscribeReply(this, msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id); + this.subscribeReply(msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id); }); return true; @@ -65,7 +69,8 @@ export default class GuessingGameModule implements IModule { } } - public onReplyThisModule = (msg: MessageLike) => { + @autobind + private onContextReply(msg: MessageLike) { if (msg.text == null) return; const exist = this.guesses.findOne({ @@ -78,7 +83,7 @@ export default class GuessingGameModule implements IModule { exist.isEnded = true; exist.endedAt = Date.now(); this.guesses.update(exist); - this.ai.unsubscribeReply(this, msg.userId); + this.unsubscribeReply(msg.userId); return; } @@ -86,7 +91,7 @@ export default class GuessingGameModule implements IModule { if (guess == null) { msg.reply(serifs.guessingGame.nan).then(reply => { - this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id); + this.subscribeReply(msg.userId, msg.isMessage, reply.id); }); } else { if (guess.length > 3) return; @@ -116,14 +121,14 @@ export default class GuessingGameModule implements IModule { if (end) { exist.isEnded = true; exist.endedAt = Date.now(); - this.ai.unsubscribeReply(this, msg.userId); + this.unsubscribeReply(msg.userId); } this.guesses.update(exist); msg.reply(text).then(reply => { if (!end) { - this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id); + this.subscribeReply(msg.userId, msg.isMessage, reply.id); } }); } diff --git a/src/modules/keyword/index.ts b/src/modules/keyword/index.ts index e7891dd..76ea27d 100644 --- a/src/modules/keyword/index.ts +++ b/src/modules/keyword/index.ts @@ -1,6 +1,6 @@ +import autobind from 'autobind-decorator'; import * as loki from 'lokijs'; -import 藍 from '../../ai'; -import IModule from '../../module'; +import Module from '../../module'; import config from '../../config'; import serifs from '../../serifs'; import getCollection from '../../utils/get-collection'; @@ -13,19 +13,17 @@ function kanaToHira(str: string) { }); } -export default class KeywordModule implements IModule { +export default class KeywordModule extends Module { public readonly name = 'keyword'; - private ai: 藍; private tokenizer: any; private learnedKeywords: loki.Collection<{ keyword: string; learnedAt: number; }>; - public install = (ai: 藍) => { - this.ai = ai; - + @autobind + public install() { //#region Init DB this.learnedKeywords = getCollection(this.ai.db, '_keyword_learnedKeywords', { indices: ['userId'] @@ -36,9 +34,12 @@ export default class KeywordModule implements IModule { this.tokenizer.command = config.mecab; setInterval(this.say, 1000 * 60 * 60); + + return {}; } - private say = async () => { + @autobind + private async say() { const tl = await this.ai.api('notes/local-timeline', { limit: 30 }); diff --git a/src/modules/ping/index.ts b/src/modules/ping/index.ts index 9bc5750..f6854b3 100644 --- a/src/modules/ping/index.ts +++ b/src/modules/ping/index.ts @@ -1,13 +1,19 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; -export default class PingModule implements IModule { +export default class PingModule extends Module { public readonly name = 'ping'; - public install = (ai: 藍) => { } + @autobind + public install() { + return { + onMention: this.onMention + }; + } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.text && msg.text.includes('ping')) { msg.reply('PONG!'); return true; diff --git a/src/modules/reversi/index.ts b/src/modules/reversi/index.ts index dd7f42c..2542a95 100644 --- a/src/modules/reversi/index.ts +++ b/src/modules/reversi/index.ts @@ -1,27 +1,23 @@ import * as childProcess from 'child_process'; -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import serifs from '../../serifs'; import config from '../../config'; import MessageLike from '../../message-like'; -import * as WebSocket from 'ws'; import Friend from '../../friend'; import getDate from '../../utils/get-date'; -export default class ReversiModule implements IModule { +export default class ReversiModule extends Module { public readonly name = 'reversi'; - private ai: 藍; - /** * リバーシストリーム */ private reversiConnection?: any; - public install = (ai: 藍) => { - if (!config.reversiEnabled) return; - - this.ai = ai; + @autobind + public install() { + if (!config.reversiEnabled) return {}; this.reversiConnection = this.ai.connection.useSharedConnection('gamesReversi'); @@ -30,9 +26,14 @@ export default class ReversiModule implements IModule { // マッチしたとき this.reversiConnection.on('matched', msg => this.onReversiGameStart(msg)); + + return { + onMention: this.onMention + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { if (msg.includes(['リバーシ', 'オセロ', 'reversi', 'othello'])) { if (config.reversiEnabled) { msg.reply(serifs.reversi.ok); @@ -50,8 +51,9 @@ export default class ReversiModule implements IModule { } } - private onReversiInviteMe = async (inviter: any) => { - console.log(`Someone invited me: @${inviter.username}`); + @autobind + private async onReversiInviteMe(inviter: any) { + this.log(`Someone invited me: @${inviter.username}`); if (config.reversiEnabled) { // 承認 @@ -65,8 +67,9 @@ export default class ReversiModule implements IModule { } } - private onReversiGameStart = (game: any) => { - console.log('enter reversi game room'); + @autobind + private onReversiGameStart(game: any) { + this.log('enter reversi game room'); // ゲームストリームに接続 const gw = this.ai.connection.connectToChannel('gamesReversiGame', { @@ -144,6 +147,7 @@ export default class ReversiModule implements IModule { }, 2000); } + @autobind private onGameEnded(game: any) { const user = game.user1Id == this.ai.account.id ? game.user2 : game.user1; diff --git a/src/modules/server/index.ts b/src/modules/server/index.ts index 59a6913..f8e891f 100644 --- a/src/modules/server/index.ts +++ b/src/modules/server/index.ts @@ -1,12 +1,11 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import serifs from '../../serifs'; import config from '../../config'; -export default class ServerModule implements IModule { +export default class ServerModule extends Module { public readonly name = 'server'; - private ai: 藍; private connection?: any; private recentStat: any; private warned = false; @@ -17,10 +16,9 @@ export default class ServerModule implements IModule { */ private statsLogs: any[] = []; - public install = (ai: 藍) => { - if (!config.serverMonitoring) return; - - this.ai = ai; + @autobind + public install() { + if (!config.serverMonitoring) return {}; this.connection = this.ai.connection.useSharedConnection('serverStats'); this.connection.on('stats', this.onStats); @@ -33,9 +31,12 @@ export default class ServerModule implements IModule { setInterval(() => { this.check(); }, 3000); + + return {}; } - private check = () => { + @autobind + private check() { const average = (arr) => arr.reduce((a, b) => a + b) / arr.length; const cpuPercentages = this.statsLogs.map(s => s && s.cpu_usage * 100 || 0); @@ -47,11 +48,13 @@ export default class ServerModule implements IModule { } } - private onStats = async (stats: any) => { + @autobind + private async onStats(stats: any) { this.recentStat = stats.body; } - private warn = () => { + @autobind + private warn() { //#region 前に警告したときから一旦落ち着いた状態を経験していなければ警告しない // 常に負荷が高いようなサーバーで無限に警告し続けるのを防ぐため if (this.warned) return; diff --git a/src/modules/timer/index.ts b/src/modules/timer/index.ts index 3125d7e..c20c69f 100644 --- a/src/modules/timer/index.ts +++ b/src/modules/timer/index.ts @@ -1,17 +1,20 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; -export default class TimerModule implements IModule { +export default class TimerModule extends Module { public readonly name = 'timer'; - private ai: 藍; - public install = (ai: 藍) => { - this.ai = ai; + @autobind + public install() { + return { + onMention: this.onMention + }; } - public onMention = (msg: MessageLike) => { + @autobind + private onMention(msg: MessageLike) { const secondsQuery = (msg.text || '').match(/([0-9]+)秒/); const minutesQuery = (msg.text || '').match(/([0-9]+)分/); const hoursQuery = (msg.text || '').match(/([0-9]+)時間/); diff --git a/src/modules/valentine/index.ts b/src/modules/valentine/index.ts index ad7bed1..98ec0da 100644 --- a/src/modules/valentine/index.ts +++ b/src/modules/valentine/index.ts @@ -1,24 +1,24 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; import Friend from '../../friend'; import serifs from '../../serifs'; -export default class ValentineModule implements IModule { +export default class ValentineModule extends Module { public readonly name = 'valentine'; - private ai: 藍; - - public install = (ai: 藍) => { - this.ai = ai; - + @autobind + public install() { this.crawleValentine(); setInterval(this.crawleValentine, 1000 * 60 * 3); + + return {}; } /** * チョコ配り */ - private crawleValentine = () => { + @autobind + private crawleValentine() { const now = new Date(); const isValentine = now.getMonth() == 1 && now.getDate() == 14; diff --git a/src/modules/welcome/index.ts b/src/modules/welcome/index.ts index 5953071..cdc6a3a 100644 --- a/src/modules/welcome/index.ts +++ b/src/modules/welcome/index.ts @@ -1,20 +1,20 @@ -import 藍 from '../../ai'; -import IModule from '../../module'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; -export default class WelcomeModule implements IModule { +export default class WelcomeModule extends Module { public readonly name = 'welcome'; - private ai: 藍; - - public install = (ai: 藍) => { - this.ai = ai; - + @autobind + public install() { const tl = this.ai.connection.useSharedConnection('localTimeline'); tl.on('note', this.onLocalNote); + + return {}; } - public onLocalNote = (note: any) => { + @autobind + private onLocalNote(note: any) { if (note.isFirstNote) { setTimeout(() => { this.ai.api('notes/create', {