This commit is contained in:
syuilo 2019-01-15 00:14:22 +09:00
parent 69bf95e6bf
commit 3d467c2629
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
20 changed files with 332 additions and 200 deletions

34
package-lock.json generated
View file

@ -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"
}

View file

@ -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",

View file

@ -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<FriendDoc>;
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<IModule['onMention']>;
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

View file

@ -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'));
});

View file

@ -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);

View file

@ -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;
};

View file

@ -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();

View file

@ -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);
});
}
}

View file

@ -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]+)/);

View file

@ -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)];

View file

@ -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', {

View file

@ -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}`;

View file

@ -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);
}
});
}

View file

@ -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
});

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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]+)時間/);

View file

@ -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;

View file

@ -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', {