ai/src/ai.ts

274 lines
6.6 KiB
TypeScript
Raw Normal View History

2018-08-11 08:26:25 +02:00
// AI CORE
2019-01-14 16:14:22 +01:00
import autobind from 'autobind-decorator';
2018-08-26 23:16:56 +02:00
import * as loki from 'lokijs';
2018-08-11 08:26:25 +02:00
import * as request from 'request-promise-native';
2019-01-14 16:14:22 +01:00
import chalk from 'chalk';
2019-01-15 10:47:22 +01:00
const delay = require('timeout-as-promise');
2018-08-11 08:26:25 +02:00
import config from './config';
2019-01-14 16:14:22 +01:00
import Module from './module';
2019-01-15 10:47:22 +01:00
import Message from './message';
2018-08-27 13:22:59 +02:00
import { FriendDoc } from './friend';
2018-08-28 23:30:48 +02:00
import { User } from './misskey/user';
2018-08-29 09:26:33 +02:00
import getCollection from './utils/get-collection';
2018-10-09 17:47:03 +02:00
import Stream from './stream';
2019-01-15 10:58:04 +01:00
import log from './utils/log';
2018-08-11 08:26:25 +02:00
2019-01-15 10:47:22 +01:00
type MentionHook = (msg: Message) => boolean | HandlerResult;
type ContextHook = (msg: Message, data?: any) => void | HandlerResult;
2019-01-14 16:14:22 +01:00
export type HandlerResult = {
reaction: string;
};
export type InstallerResult = {
2019-01-15 04:01:58 +01:00
mentionHook?: MentionHook;
contextHook?: ContextHook;
2019-01-14 16:14:22 +01:00
};
2018-08-11 08:26:25 +02:00
/**
*
*/
export default class {
2018-08-28 23:30:48 +02:00
public account: User;
2018-10-09 17:47:03 +02:00
public connection: Stream;
2019-01-14 16:14:22 +01:00
public modules: Module[] = [];
2019-01-15 04:01:58 +01:00
private mentionHooks: MentionHook[] = [];
private contextHooks: { [moduleName: string]: ContextHook } = {};
2018-08-26 23:16:56 +02:00
public db: loki;
private contexts: loki.Collection<{
2019-01-15 10:47:22 +01:00
isDm: boolean;
2018-08-26 23:16:56 +02:00
noteId?: string;
userId?: string;
module: string;
key: string;
2018-08-26 23:59:18 +02:00
data?: any;
}>;
2018-08-27 13:22:59 +02:00
public friends: loki.Collection<FriendDoc>;
2018-08-26 23:16:56 +02:00
2019-01-15 18:48:14 +01:00
/**
*
* @param account 使
* @param modules
*/
2019-01-15 18:10:42 +01:00
constructor(account: User, modules: Module[]) {
2018-08-11 08:26:25 +02:00
this.account = account;
2019-01-15 18:10:42 +01:00
this.modules = modules;
2018-08-11 08:26:25 +02:00
2019-01-15 04:29:11 +01:00
this.log('Lodaing the memory...');
2018-08-26 23:16:56 +02:00
this.db = new loki('memory.json', {
autoload: true,
autosave: true,
autosaveInterval: 1000,
2019-01-14 16:14:22 +01:00
autoloadCallback: err => {
if (err) {
2019-01-15 04:29:11 +01:00
this.log(chalk.red(`Failed to load the memory: ${err}`));
2019-01-14 16:14:22 +01:00
} else {
2019-01-15 04:29:11 +01:00
this.log(chalk.green('The memory loaded successfully'));
2019-01-15 18:10:42 +01:00
this.run();
2019-01-14 16:14:22 +01:00
}
}
2018-08-26 23:16:56 +02:00
});
}
2019-01-14 16:14:22 +01:00
@autobind
public log(msg: string) {
2019-01-15 04:29:11 +01:00
log(chalk`[{magenta AiOS}]: ${msg}`);
2019-01-14 16:14:22 +01:00
}
@autobind
2019-01-15 02:23:54 +01:00
private run() {
2018-08-26 23:16:56 +02:00
//#region Init DB
2018-08-29 09:26:33 +02:00
this.contexts = getCollection(this.db, 'contexts', {
indices: ['key']
});
this.friends = getCollection(this.db, 'friends', {
indices: ['userId']
});
2018-08-26 23:16:56 +02:00
//#endregion
2018-10-09 17:47:03 +02:00
// Init stream
this.connection = new Stream();
2018-08-11 08:26:25 +02:00
2018-10-09 17:47:03 +02:00
//#region Main stream
const mainStream = this.connection.useSharedConnection('main');
2018-08-11 08:26:25 +02:00
2018-10-09 17:47:03 +02:00
// メンションされたとき
mainStream.on('mention', data => {
if (data.userId == this.account.id) return; // 自分は弾く
2019-01-14 16:14:22 +01:00
if (data.text && data.text.startsWith('@' + this.account.username)) {
2019-01-15 10:47:22 +01:00
this.onReceiveMessage(new Message(this, data, false));
2018-10-09 17:47:03 +02:00
}
2018-08-13 23:14:47 +02:00
});
2018-10-09 17:47:03 +02:00
// 返信されたとき
mainStream.on('reply', data => {
if (data.userId == this.account.id) return; // 自分は弾く
2019-01-15 10:47:22 +01:00
this.onReceiveMessage(new Message(this, data, false));
2018-08-13 23:14:47 +02:00
});
2018-10-09 17:47:03 +02:00
// メッセージ
mainStream.on('messagingMessage', data => {
if (data.userId == this.account.id) return; // 自分は弾く
2019-01-15 10:47:22 +01:00
this.onReceiveMessage(new Message(this, data, true));
2018-08-13 23:14:47 +02:00
});
//#endregion
2018-08-11 08:26:25 +02:00
2018-10-09 17:47:03 +02:00
// Install modules
2019-01-14 16:14:22 +01:00
this.modules.forEach(m => {
this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`);
2019-01-15 18:10:42 +01:00
m.init(this);
2019-01-14 16:14:22 +01:00
const res = m.install();
if (res != null) {
2019-01-15 04:01:58 +01:00
if (res.mentionHook) this.mentionHooks.push(res.mentionHook);
if (res.contextHook) this.contextHooks[m.name] = res.contextHook;
2019-01-14 16:14:22 +01:00
}
});
this.log(chalk.green.bold('Ai am now running!'));
2018-08-13 23:14:47 +02:00
}
2019-01-15 18:48:14 +01:00
/**
*
* ()
*/
2019-01-14 16:14:22 +01:00
@autobind
2019-01-15 10:47:22 +01:00
private async onReceiveMessage(msg: Message): Promise<void> {
2019-01-15 04:29:11 +01:00
this.log(chalk.gray(`<<< An message received: ${chalk.underline(msg.id)}`));
2018-08-11 08:26:25 +02:00
2019-01-15 10:52:20 +01:00
// Ignore message if the user is a bot
// To avoid infinity reply loop.
if (msg.user.isBot) {
return;
}
2019-01-15 10:47:22 +01:00
const isNoContext = !msg.isDm && msg.replyId == null;
2019-01-15 10:34:42 +01:00
// Look up the context
2019-01-15 10:47:22 +01:00
const context = isNoContext ? null : this.contexts.findOne(msg.isDm ? {
isDm: true,
userId: msg.userId
} : {
2019-01-15 10:47:22 +01:00
isDm: false,
noteId: msg.replyId
2018-08-11 08:26:25 +02:00
});
let reaction = 'love';
2019-01-15 18:48:14 +01:00
//#region
// コンテキストがあればコンテキストフック呼び出し
// なければそれぞれのモジュールについてフックが引っかかるまで呼び出し
if (context != null) {
2019-01-15 04:01:58 +01:00
const handler = this.contextHooks[context.module];
2019-01-14 16:14:22 +01:00
const res = handler(msg, context.data);
if (res != null && typeof res === 'object') {
reaction = res.reaction;
}
} else {
2019-01-14 16:14:22 +01:00
let res: boolean | HandlerResult;
2019-01-15 04:01:58 +01:00
this.mentionHooks.some(handler => {
2019-01-14 16:14:22 +01:00
res = handler(msg);
return res === true || typeof res === 'object';
});
if (res != null && typeof res === 'object') {
reaction = res.reaction;
}
}
2019-01-15 18:48:14 +01:00
//#endregion
2019-01-15 10:47:22 +01:00
await delay(1000);
if (msg.isDm) {
// 既読にする
this.api('messaging/messages/read', {
messageId: msg.id,
});
} else {
// リアクションする
if (reaction) {
this.api('notes/reactions/create', {
noteId: msg.id,
reaction: reaction
});
}
2019-01-15 10:47:22 +01:00
}
2018-08-11 08:26:25 +02:00
}
2019-01-15 18:48:14 +01:00
/**
* 稿
*/
2019-01-14 16:14:22 +01:00
@autobind
public async post(param: any) {
const res = await this.api('notes/create', param);
return res.createdNote;
2018-08-11 08:26:25 +02:00
}
2019-01-15 18:48:14 +01:00
/**
*
*/
2019-01-14 16:14:22 +01:00
@autobind
public sendMessage(userId: any, param: any) {
return this.api('messaging/messages/create', Object.assign({
2018-08-11 08:26:25 +02:00
userId: userId,
}, param));
}
2019-01-15 18:48:14 +01:00
/**
* APIを呼び出します
*/
2019-01-14 16:14:22 +01:00
@autobind
public api(endpoint: string, param?: any) {
2018-08-11 08:26:25 +02:00
return request.post(`${config.apiUrl}/${endpoint}`, {
json: Object.assign({
i: config.i
}, param)
});
};
2019-01-15 18:48:14 +01:00
/**
*
* @param module 待ち受けるモジュール名
* @param key
* @param isDm
* @param id ID稿ID
* @param data
*/
2019-01-14 16:14:22 +01:00
@autobind
2019-01-15 10:47:22 +01:00
public subscribeReply(module: Module, key: string, isDm: boolean, id: string, data?: any) {
this.contexts.insertOne(isDm ? {
isDm: true,
userId: id,
module: module.name,
key: key,
2018-08-26 23:59:18 +02:00
data: data
} : {
2019-01-15 10:47:22 +01:00
isDm: false,
noteId: id,
module: module.name,
key: key,
2018-08-26 23:59:18 +02:00
data: data
});
}
2019-01-15 18:48:14 +01:00
/**
*
* @param module 解除するモジュール名
* @param key
*/
2019-01-14 16:14:22 +01:00
@autobind
public unsubscribeReply(module: Module, key: string) {
2018-08-26 23:16:56 +02:00
this.contexts.findAndRemove({
key: key,
module: module.name
});
}
2018-08-11 08:26:25 +02:00
}