mirror of
https://github.com/syuilo/ai.git
synced 2024-11-09 15:38:00 +00:00
Merge b2ab6778b0
into 830c9c2ecd
This commit is contained in:
commit
9ad70138d2
39
src/ai.ts
39
src/ai.ts
|
@ -9,7 +9,7 @@ import chalk from 'chalk';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import Message from '@/message.js';
|
import Message from '@/message.js';
|
||||||
import Friend, { FriendDoc } from '@/friend.js';
|
import Friend, { FriendDoc } from '@/friend.js';
|
||||||
import type { User } from '@/misskey/user.js';
|
import type { User } from '@/misskey/user.js';
|
||||||
|
@ -17,6 +17,7 @@ import Stream from '@/stream.js';
|
||||||
import log from '@/utils/log.js';
|
import log from '@/utils/log.js';
|
||||||
import { sleep } from './utils/sleep.js';
|
import { sleep } from './utils/sleep.js';
|
||||||
import pkg from '../package.json' assert { type: 'json' };
|
import pkg from '../package.json' assert { type: 'json' };
|
||||||
|
import { Note } from '@/misskey/note.js';
|
||||||
|
|
||||||
type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>;
|
type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>;
|
||||||
type ContextHook = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>;
|
type ContextHook = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>;
|
||||||
|
@ -37,6 +38,11 @@ export type Meta = {
|
||||||
lastWakingAt: number;
|
lastWakingAt: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ModuleDataDoc<Data = any> = {
|
||||||
|
module: string;
|
||||||
|
data: Data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 藍
|
* 藍
|
||||||
*/
|
*/
|
||||||
|
@ -48,6 +54,7 @@ export default class 藍 {
|
||||||
private mentionHooks: MentionHook[] = [];
|
private mentionHooks: MentionHook[] = [];
|
||||||
private contextHooks: { [moduleName: string]: ContextHook } = {};
|
private contextHooks: { [moduleName: string]: ContextHook } = {};
|
||||||
private timeoutCallbacks: { [moduleName: string]: TimeoutCallback } = {};
|
private timeoutCallbacks: { [moduleName: string]: TimeoutCallback } = {};
|
||||||
|
public installedModules: { [moduleName: string]: InstalledModule } = {};
|
||||||
public db: loki;
|
public db: loki;
|
||||||
public lastSleepedAt: number;
|
public lastSleepedAt: number;
|
||||||
|
|
||||||
|
@ -77,10 +84,8 @@ export default class 藍 {
|
||||||
* @param account 藍として使うアカウント
|
* @param account 藍として使うアカウント
|
||||||
* @param modules モジュール。先頭のモジュールほど高優先度
|
* @param modules モジュール。先頭のモジュールほど高優先度
|
||||||
*/
|
*/
|
||||||
constructor(account: User, modules: Module[]) {
|
@bindThis
|
||||||
this.account = account;
|
public static start(account: User, modules: Module[]) {
|
||||||
this.modules = modules;
|
|
||||||
|
|
||||||
let memoryDir = '.';
|
let memoryDir = '.';
|
||||||
if (config.memoryDir) {
|
if (config.memoryDir) {
|
||||||
memoryDir = config.memoryDir;
|
memoryDir = config.memoryDir;
|
||||||
|
@ -89,7 +94,7 @@ export default class 藍 {
|
||||||
|
|
||||||
this.log(`Lodaing the memory from ${file}...`);
|
this.log(`Lodaing the memory from ${file}...`);
|
||||||
|
|
||||||
this.db = new loki(file, {
|
const db = new loki(file, {
|
||||||
autoload: true,
|
autoload: true,
|
||||||
autosave: true,
|
autosave: true,
|
||||||
autosaveInterval: 1000,
|
autosaveInterval: 1000,
|
||||||
|
@ -98,7 +103,7 @@ export default class 藍 {
|
||||||
this.log(chalk.red(`Failed to load the memory: ${err}`));
|
this.log(chalk.red(`Failed to load the memory: ${err}`));
|
||||||
} else {
|
} else {
|
||||||
this.log(chalk.green('The memory loaded successfully'));
|
this.log(chalk.green('The memory loaded successfully'));
|
||||||
this.run();
|
new 藍(account, modules, db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -106,11 +111,19 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public log(msg: string) {
|
public log(msg: string) {
|
||||||
log(`[${chalk.magenta('AiOS')}]: ${msg}`);
|
藍.log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private run() {
|
private static log(msg: string) {
|
||||||
|
log(`[${chalk.magenta('AiOS')}]: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(account: User, modules: Module[], db: loki) {
|
||||||
|
this.account = account;
|
||||||
|
this.modules = modules;
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
//#region Init DB
|
//#region Init DB
|
||||||
this.meta = this.getCollection('meta', {});
|
this.meta = this.getCollection('meta', {});
|
||||||
|
|
||||||
|
@ -187,7 +200,7 @@ export default class 藍 {
|
||||||
this.modules.forEach(m => {
|
this.modules.forEach(m => {
|
||||||
this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`);
|
this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`);
|
||||||
m.init(this);
|
m.init(this);
|
||||||
const res = m.install();
|
const res = m.install(this);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
if (res.mentionHook) this.mentionHooks.push(res.mentionHook);
|
if (res.mentionHook) this.mentionHooks.push(res.mentionHook);
|
||||||
if (res.contextHook) this.contextHooks[m.name] = res.contextHook;
|
if (res.contextHook) this.contextHooks[m.name] = res.contextHook;
|
||||||
|
@ -361,7 +374,7 @@ export default class 藍 {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async post(param: any) {
|
public async post(param: any) {
|
||||||
const res = await this.api('notes/create', param);
|
const res = await this.api<{ createdNote: Note }>('notes/create', param);
|
||||||
return res.createdNote;
|
return res.createdNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,13 +393,13 @@ export default class 藍 {
|
||||||
* APIを呼び出します
|
* APIを呼び出します
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public api(endpoint: string, param?: any) {
|
public api<ReturnType = unknown>(endpoint: string, param?: any) {
|
||||||
this.log(`API: ${endpoint}`);
|
this.log(`API: ${endpoint}`);
|
||||||
return got.post(`${config.apiUrl}/${endpoint}`, {
|
return got.post(`${config.apiUrl}/${endpoint}`, {
|
||||||
json: Object.assign({
|
json: Object.assign({
|
||||||
i: config.i
|
i: config.i
|
||||||
}, param)
|
}, param)
|
||||||
}).json();
|
}).json<ReturnType>();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
101
src/config.ts
101
src/config.ts
|
@ -17,9 +17,102 @@ type Config = {
|
||||||
memoryDir?: string;
|
memoryDir?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
import config from '../config.json' assert { type: 'json' };
|
import chalk from 'chalk';
|
||||||
|
import uncheckedConfig from '../config.json' assert { type: 'json' };
|
||||||
|
import { warn } from '@/utils/log.js';
|
||||||
|
|
||||||
config.wsUrl = config.host.replace('http', 'ws');
|
function warnWithPrefix(msg: string): void {
|
||||||
config.apiUrl = config.host + '/api';
|
warn(`[Config]: ${chalk.red(msg)}`);
|
||||||
|
}
|
||||||
|
|
||||||
export default config as Config;
|
class Type<T> {
|
||||||
|
public static readonly string = new Type<string>('string');
|
||||||
|
public static readonly boolean = new Type<boolean>('boolean');
|
||||||
|
|
||||||
|
public readonly name: string;
|
||||||
|
|
||||||
|
private constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(value: unknown): value is T {
|
||||||
|
return typeof value == this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionalProperty<K extends keyof Config> {
|
||||||
|
protected readonly key: K;
|
||||||
|
protected readonly type: Type<Config[K]>
|
||||||
|
|
||||||
|
public constructor(key: K, type: Type<Config[K]>) {
|
||||||
|
this.key = key;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(config: Object): config is { [J in K]?: Config[K] } {
|
||||||
|
const key = this.key;
|
||||||
|
if (!(key in config)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const result = this.type.check((config as { [J in K]?: unknown})[key]);
|
||||||
|
if (!result) {
|
||||||
|
warnWithPrefix(`config.json: The type of property '${key}' must be ${this.type.name}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Property<K extends keyof Config> extends OptionalProperty<K> {
|
||||||
|
check(config: Object): config is { [J in K]: Config[K] } {
|
||||||
|
const result = this.key in config && this.type.check((config as { [J in K]?: unknown })[this.key]);
|
||||||
|
if (!result) {
|
||||||
|
warnWithPrefix(`config.json: Property '${this.key}': ${this.type.name} required`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Intersection<P extends unknown[]> = P extends [infer Q, ...infer R] ? Q & Intersection<R> : unknown;
|
||||||
|
|
||||||
|
function checkProperties<P extends OptionalProperty<keyof Config>[]>(config: Object, ...properties: P):
|
||||||
|
config is object & Intersection<{ [I in keyof P]: P[I] extends OptionalProperty<infer K> ? { [J in K]: Config[K] } : never }> {
|
||||||
|
// メッセージを表示するためすべてのプロパティをチェックしてから結果を返す
|
||||||
|
return properties.map(p => p.check(config)).every(c => c);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProperty<K extends keyof Config>(config: Object, key: K, value: Config[K]): asserts config is { [L in K]: Config[K] } {
|
||||||
|
(config as { [L in K]?: Config[K] })[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(config: unknown): Config {
|
||||||
|
if (!(config instanceof Object)) {
|
||||||
|
warnWithPrefix('config.json: Root object required');
|
||||||
|
} else if (
|
||||||
|
checkProperties(
|
||||||
|
config,
|
||||||
|
new Property('host', Type.string),
|
||||||
|
new OptionalProperty('serverName', Type.string),
|
||||||
|
new Property('i', Type.string),
|
||||||
|
new OptionalProperty('master', Type.string),
|
||||||
|
new Property('keywordEnabled', Type.boolean),
|
||||||
|
new Property('reversiEnabled', Type.boolean),
|
||||||
|
new Property('notingEnabled', Type.boolean),
|
||||||
|
new Property('chartEnabled', Type.boolean),
|
||||||
|
new Property('serverMonitoring', Type.boolean),
|
||||||
|
new OptionalProperty('checkEmojisEnabled', Type.boolean),
|
||||||
|
new OptionalProperty('checkEmojisAtOnce', Type.boolean),
|
||||||
|
new OptionalProperty('mecab', Type.string),
|
||||||
|
new OptionalProperty('mecabDic', Type.string),
|
||||||
|
new OptionalProperty('memoryDir', Type.string)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setProperty(config, 'wsUrl', config.host.replace('http', 'ws'));
|
||||||
|
setProperty(config, 'apiUrl', config.host + '/api');
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
throw new TypeError('config.json has an invalid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = validate(uncheckedConfig);
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
|
@ -34,6 +34,7 @@ import NotingModule from './modules/noting/index.js';
|
||||||
import PollModule from './modules/poll/index.js';
|
import PollModule from './modules/poll/index.js';
|
||||||
import ReminderModule from './modules/reminder/index.js';
|
import ReminderModule from './modules/reminder/index.js';
|
||||||
import CheckCustomEmojisModule from './modules/check-custom-emojis/index.js';
|
import CheckCustomEmojisModule from './modules/check-custom-emojis/index.js';
|
||||||
|
import { User } from '@/misskey/user.js';
|
||||||
|
|
||||||
console.log(' __ ____ _____ ___ ');
|
console.log(' __ ____ _____ ___ ');
|
||||||
console.log(' /__\\ (_ _)( _ )/ __)');
|
console.log(' /__\\ (_ _)( _ )/ __)');
|
||||||
|
@ -61,7 +62,7 @@ promiseRetry(retry => {
|
||||||
json: {
|
json: {
|
||||||
i: config.i
|
i: config.i
|
||||||
}
|
}
|
||||||
}).json().catch(retry);
|
}).json<User>().catch(retry);
|
||||||
}, {
|
}, {
|
||||||
retries: 3
|
retries: 3
|
||||||
}).then(account => {
|
}).then(account => {
|
||||||
|
@ -71,7 +72,7 @@ promiseRetry(retry => {
|
||||||
log('Starting AiOS...');
|
log('Starting AiOS...');
|
||||||
|
|
||||||
// 藍起動
|
// 藍起動
|
||||||
new 藍(account, [
|
藍.start(account, [
|
||||||
new CoreModule(),
|
new CoreModule(),
|
||||||
new EmojiModule(),
|
new EmojiModule(),
|
||||||
new EmojiReactModule(),
|
new EmojiReactModule(),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import includes from '@/utils/includes.js';
|
||||||
import or from '@/utils/or.js';
|
import or from '@/utils/or.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
import { sleep } from '@/utils/sleep.js';
|
import { sleep } from '@/utils/sleep.js';
|
||||||
|
import { Note } from '@/misskey/note.js';
|
||||||
|
|
||||||
export default class Message {
|
export default class Message {
|
||||||
private ai: 藍;
|
private ai: 藍;
|
||||||
|
@ -61,20 +62,34 @@ export default class Message {
|
||||||
this.friend = new Friend(ai, { user: this.user });
|
this.friend = new Friend(ai, { user: this.user });
|
||||||
|
|
||||||
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
|
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
|
||||||
this.ai.api('users/show', {
|
this.ai.api<User>('users/show', {
|
||||||
userId: this.userId
|
userId: this.userId
|
||||||
}).then(user => {
|
}).then(user => {
|
||||||
this.friend.updateUser(user);
|
this.friend.updateUser(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async reply(text: string, opts?: {
|
||||||
|
file?: any;
|
||||||
|
cw?: string;
|
||||||
|
renote?: string;
|
||||||
|
immediate?: boolean;
|
||||||
|
}): Promise<Note>;
|
||||||
|
|
||||||
|
public async reply(text: string | null, opts?: {
|
||||||
|
file?: any;
|
||||||
|
cw?: string;
|
||||||
|
renote?: string;
|
||||||
|
immediate?: boolean;
|
||||||
|
}): Promise<void>;
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async reply(text: string | null, opts?: {
|
public async reply(text: string | null, opts?: {
|
||||||
file?: any;
|
file?: any;
|
||||||
cw?: string;
|
cw?: string;
|
||||||
renote?: string;
|
renote?: string;
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
}) {
|
}): Promise<Note | void> {
|
||||||
if (text == null) return;
|
if (text == null) return;
|
||||||
|
|
||||||
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
|
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
export type Note = {
|
export type Note = {
|
||||||
id: string;
|
id: string;
|
||||||
text: string | null;
|
text: string | null;
|
||||||
|
cw: string | null;
|
||||||
|
userId: string;
|
||||||
reply: any | null;
|
reply: any | null;
|
||||||
poll?: {
|
poll?: {
|
||||||
choices: {
|
choices: {
|
||||||
|
|
155
src/module.ts
155
src/module.ts
|
@ -1,29 +1,36 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import 藍, { InstallerResult } from '@/ai.js';
|
import 藍, { HandlerResult, InstallerResult, ModuleDataDoc } from '@/ai.js';
|
||||||
|
import Message from '@/message.js';
|
||||||
|
|
||||||
export default abstract class Module {
|
export default abstract class Module {
|
||||||
public abstract readonly name: string;
|
public abstract readonly name: string;
|
||||||
|
|
||||||
protected ai: 藍;
|
private maybeAi?: 藍;
|
||||||
private doc: any;
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public installed?: InstalledModule;
|
||||||
|
|
||||||
public init(ai: 藍) {
|
public init(ai: 藍) {
|
||||||
this.ai = ai;
|
this.maybeAi = ai;
|
||||||
|
|
||||||
this.doc = this.ai.moduleData.findOne({
|
|
||||||
module: this.name
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.doc == null) {
|
|
||||||
this.doc = this.ai.moduleData.insertOne({
|
|
||||||
module: this.name,
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract install(): InstallerResult;
|
public abstract install(ai: 藍): InstallerResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link Module#install} の引数を使用すること
|
||||||
|
*/
|
||||||
|
protected get ai(): 藍 {
|
||||||
|
if (this.maybeAi == null) {
|
||||||
|
throw new TypeError('This module has not been initialized');
|
||||||
|
}
|
||||||
|
return this.maybeAi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link InstalledModule#log} を使用すること
|
||||||
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
protected log(msg: string) {
|
protected log(msg: string) {
|
||||||
this.ai.log(`[${this.name}]: ${msg}`);
|
this.ai.log(`[${this.name}]: ${msg}`);
|
||||||
|
@ -34,6 +41,7 @@ export default abstract class Module {
|
||||||
* @param key コンテキストを識別するためのキー
|
* @param key コンテキストを識別するためのキー
|
||||||
* @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID
|
* @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID
|
||||||
* @param data コンテキストに保存するオプションのデータ
|
* @param data コンテキストに保存するオプションのデータ
|
||||||
|
* @deprecated {@link InstalledModule#subscribeReply} を使用すること
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
protected subscribeReply(key: string | null, id: string, data?: any) {
|
protected subscribeReply(key: string | null, id: string, data?: any) {
|
||||||
|
@ -43,6 +51,7 @@ export default abstract class Module {
|
||||||
/**
|
/**
|
||||||
* 返信の待ち受けを解除します
|
* 返信の待ち受けを解除します
|
||||||
* @param key コンテキストを識別するためのキー
|
* @param key コンテキストを識別するためのキー
|
||||||
|
* @deprecated {@link InstalledModule#unsubscribeReply} を使用すること
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
protected unsubscribeReply(key: string | null) {
|
protected unsubscribeReply(key: string | null) {
|
||||||
|
@ -54,20 +63,132 @@ export default abstract class Module {
|
||||||
* このタイマーは記憶に永続化されるので、途中でプロセスを再起動しても有効です。
|
* このタイマーは記憶に永続化されるので、途中でプロセスを再起動しても有効です。
|
||||||
* @param delay ミリ秒
|
* @param delay ミリ秒
|
||||||
* @param data オプションのデータ
|
* @param data オプションのデータ
|
||||||
|
* @deprecated {@link InstalledModule#setTimeoutWithPersistence} を使用すること
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public setTimeoutWithPersistence(delay: number, data?: any) {
|
public setTimeoutWithPersistence(delay: number, data?: any) {
|
||||||
this.ai.setTimeoutWithPersistence(this, delay, data);
|
this.ai.setTimeoutWithPersistence(this, delay, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link InstalledModule#getData} を使用すること
|
||||||
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
protected getData() {
|
protected getData() {
|
||||||
|
const doc = this.ai.moduleData.findOne({
|
||||||
|
module: this.name
|
||||||
|
});
|
||||||
|
return doc?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated {@link InstalledModule#setData} を使用すること
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
protected setData(data: any) {
|
||||||
|
const doc = this.ai.moduleData.findOne({
|
||||||
|
module: this.name
|
||||||
|
});
|
||||||
|
if (doc == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doc.data = data;
|
||||||
|
this.ai.moduleData.update(doc);
|
||||||
|
if (this.installed != null) {
|
||||||
|
this.installed.updateDoc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class InstalledModule<M extends Module = Module, Data = any> implements InstallerResult {
|
||||||
|
protected readonly module: M;
|
||||||
|
|
||||||
|
protected readonly ai: 藍;
|
||||||
|
|
||||||
|
private doc: ModuleDataDoc<Data>;
|
||||||
|
|
||||||
|
constructor(module: M, ai: 藍, initialData: any = {}) {
|
||||||
|
this.module = module;
|
||||||
|
this.ai = ai;
|
||||||
|
|
||||||
|
const doc = this.ai.moduleData.findOne({
|
||||||
|
module: module.name
|
||||||
|
});
|
||||||
|
|
||||||
|
if (doc == null) {
|
||||||
|
this.doc = this.ai.moduleData.insertOne({
|
||||||
|
module: module.name,
|
||||||
|
data: initialData
|
||||||
|
}) as ModuleDataDoc<Data>;
|
||||||
|
} else {
|
||||||
|
this.doc = doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.installedModules[module.name] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
protected log(msg: string) {
|
||||||
|
this.ai.log(`[${this.module.name}]: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* コンテキストを生成し、ユーザーからの返信を待ち受けます
|
||||||
|
* @param key コンテキストを識別するためのキー
|
||||||
|
* @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID
|
||||||
|
* @param data コンテキストに保存するオプションのデータ
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
protected subscribeReply(key: string | null, id: string, data?: any) {
|
||||||
|
this.ai.subscribeReply(this.module, key, id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返信の待ち受けを解除します
|
||||||
|
* @param key コンテキストを識別するためのキー
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
protected unsubscribeReply(key: string | null) {
|
||||||
|
this.ai.unsubscribeReply(this.module, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定したミリ秒経過後に、タイムアウトコールバックを呼び出します。
|
||||||
|
* このタイマーは記憶に永続化されるので、途中でプロセスを再起動しても有効です。
|
||||||
|
* @param delay ミリ秒
|
||||||
|
* @param data オプションのデータ
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public setTimeoutWithPersistence(delay: number, data?: any) {
|
||||||
|
this.ai.setTimeoutWithPersistence(this.module, delay, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
protected getData(): Data {
|
||||||
return this.doc.data;
|
return this.doc.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
protected setData(data: any) {
|
protected setData(data: Data) {
|
||||||
this.doc.data = data;
|
this.doc.data = data;
|
||||||
this.ai.moduleData.update(this.doc);
|
this.ai.moduleData.update(this.doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public updateDoc() {
|
||||||
|
const doc = this.ai.moduleData.findOne({
|
||||||
|
module: this.module.name
|
||||||
|
});
|
||||||
|
if (doc != null) {
|
||||||
|
this.doc = doc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mentionHook?(msg: Message): Promise<boolean | HandlerResult>;
|
||||||
|
|
||||||
|
contextHook?(key: any, msg: Message, data?: any): Promise<void | boolean | HandlerResult>;
|
||||||
|
|
||||||
|
timeoutCallback?(data?: any): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,27 @@ import { renderChart } from './render-chart.js';
|
||||||
import { items } from '@/vocabulary.js';
|
import { items } from '@/vocabulary.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
|
|
||||||
|
type UserNotes = {
|
||||||
|
diffs: {
|
||||||
|
normal: number[],
|
||||||
|
reply: number[],
|
||||||
|
renote: number[]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type LocalRemotePair<T> = {
|
||||||
|
local: T,
|
||||||
|
remote: T
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserFollowing = LocalRemotePair<{
|
||||||
|
followers: {
|
||||||
|
total: number[]
|
||||||
|
}
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type Notes = LocalRemotePair<UserNotes>
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'chart';
|
public readonly name = 'chart';
|
||||||
|
|
||||||
|
@ -48,7 +69,7 @@ export default class extends Module {
|
||||||
let chart;
|
let chart;
|
||||||
|
|
||||||
if (type === 'userNotes') {
|
if (type === 'userNotes') {
|
||||||
const data = await this.ai.api('charts/user/notes', {
|
const data = await this.ai.api<UserNotes>('charts/user/notes', {
|
||||||
span: 'day',
|
span: 'day',
|
||||||
limit: 30,
|
limit: 30,
|
||||||
userId: params.user.id
|
userId: params.user.id
|
||||||
|
@ -65,7 +86,7 @@ export default class extends Module {
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} else if (type === 'followers') {
|
} else if (type === 'followers') {
|
||||||
const data = await this.ai.api('charts/user/following', {
|
const data = await this.ai.api<UserFollowing>('charts/user/following', {
|
||||||
span: 'day',
|
span: 'day',
|
||||||
limit: 30,
|
limit: 30,
|
||||||
userId: params.user.id
|
userId: params.user.id
|
||||||
|
@ -80,7 +101,7 @@ export default class extends Module {
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} else if (type === 'notes') {
|
} else if (type === 'notes') {
|
||||||
const data = await this.ai.api('charts/notes', {
|
const data = await this.ai.api<Notes>('charts/notes', {
|
||||||
span: 'day',
|
span: 'day',
|
||||||
limit: 30,
|
limit: 30,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,31 +1,35 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import loki from 'lokijs';
|
import loki from 'lokijs';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
import Message from '@/message.js';
|
import Message from '@/message.js';
|
||||||
|
import 藍 from '@/ai.js';
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'checkCustomEmojis';
|
public readonly name = 'checkCustomEmojis';
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public install(ai: 藍) {
|
||||||
|
if (!config.checkEmojisEnabled) return {};
|
||||||
|
return new Installed(this, ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Installed extends InstalledModule {
|
||||||
private lastEmoji: loki.Collection<{
|
private lastEmoji: loki.Collection<{
|
||||||
id: string;
|
id: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@bindThis
|
constructor(module: Module, ai: 藍) {
|
||||||
public install() {
|
super(module, ai);
|
||||||
if (!config.checkEmojisEnabled) return {};
|
|
||||||
this.lastEmoji = this.ai.getCollection('lastEmoji', {
|
this.lastEmoji = this.ai.getCollection('lastEmoji', {
|
||||||
indices: ['id']
|
indices: ['id']
|
||||||
});
|
});
|
||||||
|
|
||||||
this.timeCheck();
|
this.timeCheck();
|
||||||
setInterval(this.timeCheck, 1000 * 60 * 3);
|
setInterval(this.timeCheck, 1000 * 60 * 3);
|
||||||
|
|
||||||
return {
|
|
||||||
mentionHook: this.mentionHook
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -141,7 +145,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async mentionHook(msg: Message) {
|
public async mentionHook(msg: Message) {
|
||||||
if (!msg.includes(['カスタムえもじチェック','カスタムえもじを調べて','カスタムえもじを確認'])) {
|
if (!msg.includes(['カスタムえもじチェック','カスタムえもじを調べて','カスタムえもじを確認'])) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,19 +3,16 @@ import { parse } from 'twemoji-parser';
|
||||||
|
|
||||||
import type { Note } from '@/misskey/note.js';
|
import type { Note } from '@/misskey/note.js';
|
||||||
import Module from '@/module.js';
|
import Module from '@/module.js';
|
||||||
import Stream from '@/stream.js';
|
|
||||||
import includes from '@/utils/includes.js';
|
import includes from '@/utils/includes.js';
|
||||||
import { sleep } from '@/utils/sleep.js';
|
import { sleep } from '@/utils/sleep.js';
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'emoji-react';
|
public readonly name = 'emoji-react';
|
||||||
|
|
||||||
private htl: ReturnType<Stream['useSharedConnection']>;
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public install() {
|
public install() {
|
||||||
this.htl = this.ai.connection.useSharedConnection('homeTimeline');
|
const htl = this.ai.connection.useSharedConnection('homeTimeline');
|
||||||
this.htl.on('note', this.onNote);
|
htl.on('note', this.onNote);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,42 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import loki from 'lokijs';
|
import loki from 'lokijs';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import Message from '@/message.js';
|
import Message from '@/message.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
|
import 藍, { InstallerResult } from '@/ai.js';
|
||||||
|
|
||||||
export default class extends Module {
|
type Guesses = loki.Collection<{
|
||||||
public readonly name = 'guessingGame';
|
|
||||||
|
|
||||||
private guesses: loki.Collection<{
|
|
||||||
userId: string;
|
userId: string;
|
||||||
secret: number;
|
secret: number;
|
||||||
tries: number[];
|
tries: number[];
|
||||||
isEnded: boolean;
|
isEnded: boolean;
|
||||||
startedAt: number;
|
startedAt: number;
|
||||||
endedAt: number | null;
|
endedAt: number | null;
|
||||||
}>;
|
}>
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = 'guessingGame';
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public install() {
|
public install(ai: 藍) {
|
||||||
this.guesses = this.ai.getCollection('guessingGame', {
|
const guesses = ai.getCollection('guessingGame', {
|
||||||
indices: ['userId']
|
indices: ['userId']
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return new Installed(this, ai, guesses);
|
||||||
mentionHook: this.mentionHook,
|
}
|
||||||
contextHook: this.contextHook
|
}
|
||||||
};
|
|
||||||
|
class Installed extends InstalledModule implements InstallerResult {
|
||||||
|
private guesses: Guesses;
|
||||||
|
|
||||||
|
constructor(module: Module, ai: 藍, guesses: Guesses) {
|
||||||
|
super(module, ai);
|
||||||
|
this.guesses = guesses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async mentionHook(msg: Message) {
|
public async mentionHook(msg: Message) {
|
||||||
if (!msg.includes(['数当て', '数あて'])) return false;
|
if (!msg.includes(['数当て', '数あて'])) return false;
|
||||||
|
|
||||||
const exist = this.guesses.findOne({
|
const exist = this.guesses.findOne({
|
||||||
|
@ -56,7 +63,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async contextHook(key: any, msg: Message) {
|
public async contextHook(key: any, msg: Message) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return;
|
||||||
|
|
||||||
const exist = this.guesses.findOne({
|
const exist = this.guesses.findOne({
|
||||||
|
@ -114,14 +121,14 @@ export default class extends Module {
|
||||||
if (end) {
|
if (end) {
|
||||||
exist.isEnded = true;
|
exist.isEnded = true;
|
||||||
exist.endedAt = Date.now();
|
exist.endedAt = Date.now();
|
||||||
this.unsubscribeReply(key);
|
this.ai.unsubscribeReply(this.module, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.guesses.update(exist);
|
this.guesses.update(exist);
|
||||||
|
|
||||||
msg.reply(text).then(reply => {
|
msg.reply(text).then(reply => {
|
||||||
if (!end) {
|
if (!end) {
|
||||||
this.subscribeReply(msg.userId, reply.id);
|
this.ai.subscribeReply(this.module, msg.userId, reply.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import loki from 'lokijs';
|
import loki from 'lokijs';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import Message from '@/message.js';
|
import Message from '@/message.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import type { User } from '@/misskey/user.js';
|
import type { User } from '@/misskey/user.js';
|
||||||
import { acct } from '@/utils/acct.js';
|
import { acct } from '@/utils/acct.js';
|
||||||
|
import 藍, { InstallerResult } from '@/ai.js';
|
||||||
|
|
||||||
type Game = {
|
type Game = {
|
||||||
votes: {
|
votes: {
|
||||||
|
@ -25,23 +26,28 @@ const limitMinutes = 10;
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'kazutori';
|
public readonly name = 'kazutori';
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public install(ai: 藍) {
|
||||||
|
return new Installed(this, ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Installed extends InstalledModule {
|
||||||
|
|
||||||
private games: loki.Collection<Game>;
|
private games: loki.Collection<Game>;
|
||||||
|
|
||||||
@bindThis
|
constructor(module: Module, ai: 藍) {
|
||||||
public install() {
|
super(module, ai);
|
||||||
this.games = this.ai.getCollection('kazutori');
|
this.games = this.ai.getCollection('kazutori');
|
||||||
|
|
||||||
this.crawleGameEnd();
|
this.crawleGameEnd();
|
||||||
setInterval(this.crawleGameEnd, 1000);
|
setInterval(this.crawleGameEnd, 1000);
|
||||||
|
|
||||||
return {
|
return this;
|
||||||
mentionHook: this.mentionHook,
|
|
||||||
contextHook: this.contextHook
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async mentionHook(msg: Message) {
|
public async mentionHook(msg: Message) {
|
||||||
if (!msg.includes(['数取り'])) return false;
|
if (!msg.includes(['数取り'])) return false;
|
||||||
|
|
||||||
const games = this.games.find({});
|
const games = this.games.find({});
|
||||||
|
@ -83,7 +89,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async contextHook(key: any, msg: Message) {
|
public async contextHook(key: any, msg: Message) {
|
||||||
if (msg.text == null) return {
|
if (msg.text == null) return {
|
||||||
reaction: 'hmm'
|
reaction: 'hmm'
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import loki from 'lokijs';
|
import loki from 'lokijs';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import { mecab } from './mecab.js';
|
import { mecab } from './mecab.js';
|
||||||
|
import 藍 from '@/ai.js';
|
||||||
|
import { Note } from '@/misskey/note.js';
|
||||||
|
|
||||||
function kanaToHira(str: string) {
|
function kanaToHira(str: string) {
|
||||||
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
||||||
|
@ -15,31 +17,37 @@ function kanaToHira(str: string) {
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'keyword';
|
public readonly name = 'keyword';
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public install(ai: 藍) {
|
||||||
|
if (config.keywordEnabled) {
|
||||||
|
new Installed(this, ai);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Installed extends InstalledModule {
|
||||||
private learnedKeywords: loki.Collection<{
|
private learnedKeywords: loki.Collection<{
|
||||||
keyword: string;
|
keyword: string;
|
||||||
learnedAt: number;
|
learnedAt: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@bindThis
|
constructor(module: Module, ai: 藍) {
|
||||||
public install() {
|
super(module, ai);
|
||||||
if (!config.keywordEnabled) return {};
|
|
||||||
|
|
||||||
this.learnedKeywords = this.ai.getCollection('_keyword_learnedKeywords', {
|
this.learnedKeywords = this.ai.getCollection('_keyword_learnedKeywords', {
|
||||||
indices: ['userId']
|
indices: ['userId']
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(this.learn, 1000 * 60 * 60);
|
setInterval(this.learn, 1000 * 60 * 60);
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async learn() {
|
private async learn() {
|
||||||
const tl = await this.ai.api('notes/local-timeline', {
|
const tl = await this.ai.api<Note[]>('notes/local-timeline', {
|
||||||
limit: 30
|
limit: 30
|
||||||
});
|
});
|
||||||
|
|
||||||
const interestedNotes = tl.filter(note =>
|
const interestedNotes = tl.filter((note): note is Note & { text: string } =>
|
||||||
note.userId !== this.ai.account.id &&
|
note.userId !== this.ai.account.id &&
|
||||||
note.text != null &&
|
note.text != null &&
|
||||||
note.cw == null);
|
note.cw == null);
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import loki from 'lokijs';
|
import loki from 'lokijs';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import Message from '@/message.js';
|
import Message from '@/message.js';
|
||||||
import serifs, { getSerif } from '@/serifs.js';
|
import serifs, { getSerif } from '@/serifs.js';
|
||||||
import { acct } from '@/utils/acct.js';
|
import { acct } from '@/utils/acct.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
|
import 藍 from '@/ai.js';
|
||||||
|
|
||||||
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
|
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'reminder';
|
public readonly name = 'reminder';
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public install(ai: 藍) {
|
||||||
|
return new Installed(this, ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Installed extends InstalledModule {
|
||||||
private reminds: loki.Collection<{
|
private reminds: loki.Collection<{
|
||||||
userId: string;
|
userId: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -20,21 +28,15 @@ export default class extends Module {
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@bindThis
|
constructor(module: Module, ai: 藍) {
|
||||||
public install() {
|
super(module, ai);
|
||||||
this.reminds = this.ai.getCollection('reminds', {
|
this.reminds = this.ai.getCollection('reminds', {
|
||||||
indices: ['userId', 'id']
|
indices: ['userId', 'id']
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
mentionHook: this.mentionHook,
|
|
||||||
contextHook: this.contextHook,
|
|
||||||
timeoutCallback: this.timeoutCallback,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async mentionHook(msg: Message) {
|
public async mentionHook(msg: Message) {
|
||||||
let text = msg.extractedText.toLowerCase();
|
let text = msg.extractedText.toLowerCase();
|
||||||
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
|
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async contextHook(key: any, msg: Message, data: any) {
|
public async contextHook(key: any, msg: Message, data: any) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return;
|
||||||
|
|
||||||
const remind = this.reminds.findOne({
|
const remind = this.reminds.findOne({
|
||||||
|
@ -129,7 +131,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async timeoutCallback(data) {
|
public async timeoutCallback(data) {
|
||||||
const remind = this.reminds.findOne({
|
const remind = this.reminds.findOne({
|
||||||
id: data.id
|
id: data.id
|
||||||
});
|
});
|
||||||
|
@ -147,7 +149,7 @@ export default class extends Module {
|
||||||
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
|
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
|
||||||
text: acct(friend.doc.user) + ' ' + serifs.reminder.notify(friend.name)
|
text: acct(friend.doc.user) + ' ' + serifs.reminder.notify(friend.name)
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// renote対象が消されていたらリマインダー解除
|
// renote対象が消されていたらリマインダー解除
|
||||||
if (err.statusCode === 400) {
|
if (err.statusCode === 400) {
|
||||||
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);
|
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as Reversi from './engine.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import type { User } from '@/misskey/user.js';
|
import type { User } from '@/misskey/user.js';
|
||||||
|
import { Note } from '@/misskey/note.js';
|
||||||
|
|
||||||
function getUserName(user) {
|
function getUserName(user) {
|
||||||
return user.name || user.username;
|
return user.name || user.username;
|
||||||
|
@ -24,11 +25,35 @@ const titles = [
|
||||||
];
|
];
|
||||||
|
|
||||||
class Session {
|
class Session {
|
||||||
private account: User;
|
private maybeAccount?: User;
|
||||||
private game: any;
|
private game: any;
|
||||||
private form: any;
|
private form: any;
|
||||||
private engine: Reversi.Game;
|
private maybeEngine?: Reversi.Game;
|
||||||
private botColor: Reversi.Color;
|
private maybeBotColor?: Reversi.Color;
|
||||||
|
|
||||||
|
private get account(): User {
|
||||||
|
const maybeAccount = this.maybeAccount;
|
||||||
|
if (maybeAccount == null) {
|
||||||
|
throw new Error('Have not received "_init_" message');
|
||||||
|
}
|
||||||
|
return maybeAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get engine(): Reversi.Game {
|
||||||
|
const maybeEngine = this.maybeEngine;
|
||||||
|
if (maybeEngine == null) {
|
||||||
|
throw new Error('Have not received "started" message');
|
||||||
|
}
|
||||||
|
return maybeEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get botColor(): Reversi.Color {
|
||||||
|
const maybeBotColor = this.maybeBotColor;
|
||||||
|
if (maybeBotColor == null) {
|
||||||
|
throw new Error('Have not received "started" message');
|
||||||
|
}
|
||||||
|
return maybeBotColor;
|
||||||
|
}
|
||||||
|
|
||||||
private appliedOps: string[] = [];
|
private appliedOps: string[] = [];
|
||||||
|
|
||||||
|
@ -100,7 +125,7 @@ class Session {
|
||||||
private onInit = (msg: any) => {
|
private onInit = (msg: any) => {
|
||||||
this.game = msg.game;
|
this.game = msg.game;
|
||||||
this.form = msg.form;
|
this.form = msg.form;
|
||||||
this.account = msg.account;
|
this.maybeAccount = msg.account;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +146,7 @@ class Session {
|
||||||
});
|
});
|
||||||
|
|
||||||
// リバーシエンジン初期化
|
// リバーシエンジン初期化
|
||||||
this.engine = new Reversi.Game(this.game.map, {
|
this.maybeEngine = new Reversi.Game(this.game.map, {
|
||||||
isLlotheo: this.game.isLlotheo,
|
isLlotheo: this.game.isLlotheo,
|
||||||
canPutEverywhere: this.game.canPutEverywhere,
|
canPutEverywhere: this.game.canPutEverywhere,
|
||||||
loopedBoard: this.game.loopedBoard
|
loopedBoard: this.game.loopedBoard
|
||||||
|
@ -198,7 +223,7 @@ class Session {
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
this.botColor = this.game.user1Id == this.account.id && this.game.black == 1 || this.game.user2Id == this.account.id && this.game.black == 2;
|
this.maybeBotColor = this.game.user1Id == this.account.id && this.game.black == 1 || this.game.user2Id == this.account.id && this.game.black == 2;
|
||||||
|
|
||||||
if (this.botColor) {
|
if (this.botColor) {
|
||||||
this.think();
|
this.think();
|
||||||
|
@ -454,7 +479,7 @@ class Session {
|
||||||
try {
|
try {
|
||||||
const res = await got.post(`${config.host}/api/notes/create`, {
|
const res = await got.post(`${config.host}/api/notes/create`, {
|
||||||
json: body
|
json: body
|
||||||
}).json();
|
}).json<{ createdNote: Note }>();
|
||||||
|
|
||||||
return res.createdNote;
|
return res.createdNote;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import Module from '@/module.js';
|
import Module, { InstalledModule } from '@/module.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import config from '@/config.js';
|
import config from '@/config.js';
|
||||||
|
import 藍 from '@/ai.js';
|
||||||
|
|
||||||
export default class extends Module {
|
export default class extends Module {
|
||||||
public readonly name = 'server';
|
public readonly name = 'server';
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public install(ai: 藍) {
|
||||||
|
if (config.serverMonitoring) {
|
||||||
|
new Installed(this, ai);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Installed extends InstalledModule {
|
||||||
private connection?: any;
|
private connection?: any;
|
||||||
private recentStat: any;
|
private recentStat: any;
|
||||||
private warned = false;
|
private warned = false;
|
||||||
private lastWarnedAt: number;
|
private lastWarnedAt?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1秒毎のログ1分間分
|
* 1秒毎のログ1分間分
|
||||||
*/
|
*/
|
||||||
private statsLogs: any[] = [];
|
private statsLogs: any[] = [];
|
||||||
|
|
||||||
@bindThis
|
constructor(module: Module, ai: 藍) {
|
||||||
public install() {
|
super(module, ai);
|
||||||
if (!config.serverMonitoring) return {};
|
|
||||||
|
|
||||||
this.connection = this.ai.connection.useSharedConnection('serverStats');
|
this.connection = this.ai.connection.useSharedConnection('serverStats');
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on('stats', this.onStats);
|
||||||
|
@ -31,8 +41,6 @@ export default class extends Module {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.check();
|
this.check();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
export default function(msg: string) {
|
export default function(msg: string) {
|
||||||
|
console.log(createMessage(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warn(msg: string) {
|
||||||
|
console.warn(createMessage(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMessage(msg: string) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const date = `${zeroPad(now.getHours())}:${zeroPad(now.getMinutes())}:${zeroPad(now.getSeconds())}`;
|
const date = `${zeroPad(now.getHours())}:${zeroPad(now.getMinutes())}:${zeroPad(now.getSeconds())}`;
|
||||||
console.log(`${chalk.gray(date)} ${msg}`);
|
return `${chalk.gray(date)} ${msg}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function zeroPad(num: number, length: number = 2): string {
|
function zeroPad(num: number, length: number = 2): string {
|
||||||
|
|
Loading…
Reference in a new issue