This commit is contained in:
Take-John 2024-03-30 20:08:26 +09:00 committed by GitHub
commit 7fe378ca50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 457 additions and 121 deletions

View file

@ -9,7 +9,7 @@ import chalk from 'chalk';
import { v4 as uuid } from 'uuid';
import config from '@/config.js';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import Message from '@/message.js';
import Friend, { FriendDoc } from '@/friend.js';
import type { User } from '@/misskey/user.js';
@ -17,6 +17,7 @@ import Stream from '@/stream.js';
import log from '@/utils/log.js';
import { sleep } from './utils/sleep.js';
import pkg from '../package.json' assert { type: 'json' };
import { Note } from '@/misskey/note.js';
type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>;
type ContextHook = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>;
@ -37,6 +38,11 @@ export type Meta = {
lastWakingAt: number;
};
export type ModuleDataDoc<Data = any> = {
module: string;
data: Data;
}
/**
*
*/
@ -77,10 +83,8 @@ export default class 藍 {
* @param account 使
* @param modules
*/
constructor(account: User, modules: Module[]) {
this.account = account;
this.modules = modules;
@bindThis
public static start(account: User, modules: Module[]) {
let memoryDir = '.';
if (config.memoryDir) {
memoryDir = config.memoryDir;
@ -89,7 +93,7 @@ export default class 藍 {
this.log(`Lodaing the memory from ${file}...`);
this.db = new loki(file, {
const db = new loki(file, {
autoload: true,
autosave: true,
autosaveInterval: 1000,
@ -98,7 +102,7 @@ export default class 藍 {
this.log(chalk.red(`Failed to load the memory: ${err}`));
} else {
this.log(chalk.green('The memory loaded successfully'));
this.run();
new (account, modules, db);
}
}
});
@ -106,11 +110,19 @@ export default class 藍 {
@bindThis
public log(msg: string) {
log(`[${chalk.magenta('AiOS')}]: ${msg}`);
.log(msg);
}
@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
this.meta = this.getCollection('meta', {});
@ -187,7 +199,7 @@ export default class 藍 {
this.modules.forEach(m => {
this.log(`Installing ${chalk.cyan.italic(m.name)}\tmodule...`);
m.init(this);
const res = m.install();
const res = m.install(this);
if (res != null) {
if (res.mentionHook) this.mentionHooks.push(res.mentionHook);
if (res.contextHook) this.contextHooks[m.name] = res.contextHook;
@ -361,7 +373,7 @@ export default class 藍 {
*/
@bindThis
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;
}
@ -380,13 +392,13 @@ export default class 藍 {
* APIを呼び出します
*/
@bindThis
public api(endpoint: string, param?: any) {
public api<ReturnType = unknown>(endpoint: string, param?: any) {
this.log(`API: ${endpoint}`);
return got.post(`${config.apiUrl}/${endpoint}`, {
json: Object.assign({
i: config.i
}, param)
}).json();
}).json<ReturnType>();
};
/**

View file

@ -17,9 +17,102 @@ type Config = {
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');
config.apiUrl = config.host + '/api';
function warnWithPrefix(msg: string): void {
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;

View file

@ -34,6 +34,7 @@ import NotingModule from './modules/noting/index.js';
import PollModule from './modules/poll/index.js';
import ReminderModule from './modules/reminder/index.js';
import CheckCustomEmojisModule from './modules/check-custom-emojis/index.js';
import { User } from '@/misskey/user.js';
console.log(' __ ____ _____ ___ ');
console.log(' /__\\ (_ _)( _ )/ __)');
@ -61,7 +62,7 @@ promiseRetry(retry => {
json: {
i: config.i
}
}).json().catch(retry);
}).json<User>().catch(retry);
}, {
retries: 3
}).then(account => {
@ -71,7 +72,7 @@ promiseRetry(retry => {
log('Starting AiOS...');
// 藍起動
new (account, [
.start(account, [
new CoreModule(),
new EmojiModule(),
new EmojiReactModule(),

View file

@ -8,6 +8,7 @@ import includes from '@/utils/includes.js';
import or from '@/utils/or.js';
import config from '@/config.js';
import { sleep } from '@/utils/sleep.js';
import { Note } from '@/misskey/note.js';
export default class Message {
private ai: ;
@ -61,20 +62,34 @@ export default class Message {
this.friend = new Friend(ai, { user: this.user });
// メッセージなどに付いているユーザー情報は省略されている場合があるので完全なユーザー情報を持ってくる
this.ai.api('users/show', {
this.ai.api<User>('users/show', {
userId: this.userId
}).then(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
public async reply(text: string | null, opts?: {
file?: any;
cw?: string;
renote?: string;
immediate?: boolean;
}) {
}): Promise<Note | void> {
if (text == null) return;
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);

View file

@ -1,6 +1,8 @@
export type Note = {
id: string;
text: string | null;
cw: string | null;
userId: string;
reply: any | null;
poll?: {
choices: {

View file

@ -1,29 +1,36 @@
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 {
public abstract readonly name: string;
protected ai: ;
private doc: any;
private maybeAi?: ;
/**
* @deprecated
*/
public installed?: InstalledModule;
public init(ai: ) {
this.ai = ai;
this.doc = this.ai.moduleData.findOne({
module: this.name
});
if (this.doc == null) {
this.doc = this.ai.moduleData.insertOne({
module: this.name,
data: {}
});
}
this.maybeAi = ai;
}
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
protected log(msg: string) {
this.ai.log(`[${this.name}]: ${msg}`);
@ -34,6 +41,7 @@ export default abstract class Module {
* @param key
* @param id ID稿ID
* @param data
* @deprecated {@link InstalledModule#subscribeReply} 使
*/
@bindThis
protected subscribeReply(key: string | null, id: string, data?: any) {
@ -43,6 +51,7 @@ export default abstract class Module {
/**
*
* @param key
* @deprecated {@link InstalledModule#unsubscribeReply} 使
*/
@bindThis
protected unsubscribeReply(key: string | null) {
@ -54,20 +63,138 @@ export default abstract class Module {
*
* @param delay
* @param data
* @deprecated {@link InstalledModule#setTimeoutWithPersistence} 使
*/
@bindThis
public setTimeoutWithPersistence(delay: number, data?: any) {
this.ai.setTimeoutWithPersistence(this, delay, data);
}
/**
* @deprecated {@link InstalledModule#getData} 使
*/
@bindThis
protected getData() {
let doc = this.ai.moduleData.findOne({
module: this.name
});
if (doc == null) {
doc = this.ai.moduleData.insertOne({
module: this.name,
data: {}
});
}
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;
}
module.installed = 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;
}
@bindThis
protected setData(data: any) {
protected setData(data: Data) {
this.doc.data = data;
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;
}

View file

@ -6,6 +6,27 @@ import { renderChart } from './render-chart.js';
import { items } from '@/vocabulary.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 {
public readonly name = 'chart';
@ -48,7 +69,7 @@ export default class extends Module {
let chart;
if (type === 'userNotes') {
const data = await this.ai.api('charts/user/notes', {
const data = await this.ai.api<UserNotes>('charts/user/notes', {
span: 'day',
limit: 30,
userId: params.user.id
@ -65,7 +86,7 @@ export default class extends Module {
}]
};
} 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',
limit: 30,
userId: params.user.id
@ -80,7 +101,7 @@ export default class extends Module {
}]
};
} else if (type === 'notes') {
const data = await this.ai.api('charts/notes', {
const data = await this.ai.api<Notes>('charts/notes', {
span: 'day',
limit: 30,
});

View file

@ -1,31 +1,35 @@
import { bindThis } from '@/decorators.js';
import loki from 'lokijs';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import serifs from '@/serifs.js';
import config from '@/config.js';
import Message from '@/message.js';
import from '@/ai.js';
export default class extends Module {
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<{
id: string;
updatedAt: number;
}>;
@bindThis
public install() {
if (!config.checkEmojisEnabled) return {};
constructor(module: Module, ai: ) {
super(module, ai);
this.lastEmoji = this.ai.getCollection('lastEmoji', {
indices: ['id']
});
this.timeCheck();
setInterval(this.timeCheck, 1000 * 60 * 3);
return {
mentionHook: this.mentionHook
};
}
@bindThis
@ -141,7 +145,7 @@ export default class extends Module {
}
@bindThis
private async mentionHook(msg: Message) {
public async mentionHook(msg: Message) {
if (!msg.includes(['カスタムえもじチェック','カスタムえもじを調べて','カスタムえもじを確認'])) {
return false;
} else {

View file

@ -3,19 +3,16 @@ import { parse } from 'twemoji-parser';
import type { Note } from '@/misskey/note.js';
import Module from '@/module.js';
import Stream from '@/stream.js';
import includes from '@/utils/includes.js';
import { sleep } from '@/utils/sleep.js';
export default class extends Module {
public readonly name = 'emoji-react';
private htl: ReturnType<Stream['useSharedConnection']>;
@bindThis
public install() {
this.htl = this.ai.connection.useSharedConnection('homeTimeline');
this.htl.on('note', this.onNote);
const htl = this.ai.connection.useSharedConnection('homeTimeline');
htl.on('note', this.onNote);
return {};
}

View file

@ -1,35 +1,42 @@
import { bindThis } from '@/decorators.js';
import loki from 'lokijs';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import Message from '@/message.js';
import serifs from '@/serifs.js';
import , { InstallerResult } from '@/ai.js';
type Guesses = loki.Collection<{
userId: string;
secret: number;
tries: number[];
isEnded: boolean;
startedAt: number;
endedAt: number | null;
}>
export default class extends Module {
public readonly name = 'guessingGame';
private guesses: loki.Collection<{
userId: string;
secret: number;
tries: number[];
isEnded: boolean;
startedAt: number;
endedAt: number | null;
}>;
@bindThis
public install() {
this.guesses = this.ai.getCollection('guessingGame', {
public install(ai: ) {
const guesses = ai.getCollection('guessingGame', {
indices: ['userId']
});
return {
mentionHook: this.mentionHook,
contextHook: this.contextHook
};
return new Installed(this, ai, guesses);
}
}
class Installed extends InstalledModule implements InstallerResult {
private guesses: Guesses;
constructor(module: Module, ai: , guesses: Guesses) {
super(module, ai);
this.guesses = guesses;
}
@bindThis
private async mentionHook(msg: Message) {
public async mentionHook(msg: Message) {
if (!msg.includes(['数当て', '数あて'])) return false;
const exist = this.guesses.findOne({
@ -56,7 +63,7 @@ export default class extends Module {
}
@bindThis
private async contextHook(key: any, msg: Message) {
public async contextHook(key: any, msg: Message) {
if (msg.text == null) return;
const exist = this.guesses.findOne({
@ -114,14 +121,14 @@ export default class extends Module {
if (end) {
exist.isEnded = true;
exist.endedAt = Date.now();
this.unsubscribeReply(key);
this.ai.unsubscribeReply(this.module, key);
}
this.guesses.update(exist);
msg.reply(text).then(reply => {
if (!end) {
this.subscribeReply(msg.userId, reply.id);
this.ai.subscribeReply(this.module, msg.userId, reply.id);
}
});
}

View file

@ -1,10 +1,11 @@
import { bindThis } from '@/decorators.js';
import loki from 'lokijs';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import Message from '@/message.js';
import serifs from '@/serifs.js';
import type { User } from '@/misskey/user.js';
import { acct } from '@/utils/acct.js';
import , { InstallerResult } from '@/ai.js';
type Game = {
votes: {
@ -25,23 +26,28 @@ const limitMinutes = 10;
export default class extends Module {
public readonly name = 'kazutori';
@bindThis
public install(ai: ) {
return new Installed(this, ai);
}
}
class Installed extends InstalledModule {
private games: loki.Collection<Game>;
@bindThis
public install() {
constructor(module: Module, ai: ) {
super(module, ai);
this.games = this.ai.getCollection('kazutori');
this.crawleGameEnd();
setInterval(this.crawleGameEnd, 1000);
return {
mentionHook: this.mentionHook,
contextHook: this.contextHook
};
return this;
}
@bindThis
private async mentionHook(msg: Message) {
public async mentionHook(msg: Message) {
if (!msg.includes(['数取り'])) return false;
const games = this.games.find({});
@ -83,7 +89,7 @@ export default class extends Module {
}
@bindThis
private async contextHook(key: any, msg: Message) {
public async contextHook(key: any, msg: Message) {
if (msg.text == null) return {
reaction: 'hmm'
};

View file

@ -1,9 +1,11 @@
import { bindThis } from '@/decorators.js';
import loki from 'lokijs';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import config from '@/config.js';
import serifs from '@/serifs.js';
import { mecab } from './mecab.js';
import from '@/ai.js';
import { Note } from '@/misskey/note.js';
function kanaToHira(str: string) {
return str.replace(/[\u30a1-\u30f6]/g, match => {
@ -15,31 +17,37 @@ function kanaToHira(str: string) {
export default class extends Module {
public readonly name = 'keyword';
@bindThis
public install(ai: ) {
if (config.keywordEnabled) {
new Installed(this, ai);
}
return {};
}
}
class Installed extends InstalledModule {
private learnedKeywords: loki.Collection<{
keyword: string;
learnedAt: number;
}>;
@bindThis
public install() {
if (!config.keywordEnabled) return {};
constructor(module: Module, ai: ) {
super(module, ai);
this.learnedKeywords = this.ai.getCollection('_keyword_learnedKeywords', {
indices: ['userId']
});
setInterval(this.learn, 1000 * 60 * 60);
return {};
}
@bindThis
private async learn() {
const tl = await this.ai.api('notes/local-timeline', {
const tl = await this.ai.api<Note[]>('notes/local-timeline', {
limit: 30
});
const interestedNotes = tl.filter(note =>
const interestedNotes = tl.filter((note): note is Note & { text: string } =>
note.userId !== this.ai.account.id &&
note.text != null &&
note.cw == null);

View file

@ -1,16 +1,24 @@
import { bindThis } from '@/decorators.js';
import loki from 'lokijs';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import Message from '@/message.js';
import serifs, { getSerif } from '@/serifs.js';
import { acct } from '@/utils/acct.js';
import config from '@/config.js';
import from '@/ai.js';
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
export default class extends Module {
public readonly name = 'reminder';
@bindThis
public install(ai: ) {
return new Installed(this, ai);
}
}
class Installed extends InstalledModule {
private reminds: loki.Collection<{
userId: string;
id: string;
@ -20,21 +28,15 @@ export default class extends Module {
createdAt: number;
}>;
@bindThis
public install() {
constructor(module: Module, ai: ) {
super(module, ai);
this.reminds = this.ai.getCollection('reminds', {
indices: ['userId', 'id']
});
return {
mentionHook: this.mentionHook,
contextHook: this.contextHook,
timeoutCallback: this.timeoutCallback,
};
}
@bindThis
private async mentionHook(msg: Message) {
public async mentionHook(msg: Message) {
let text = msg.extractedText.toLowerCase();
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
@ -99,7 +101,7 @@ export default class extends Module {
}
@bindThis
private async contextHook(key: any, msg: Message, data: any) {
public async contextHook(key: any, msg: Message, data: any) {
if (msg.text == null) return;
const remind = this.reminds.findOne({
@ -129,7 +131,7 @@ export default class extends Module {
}
@bindThis
private async timeoutCallback(data) {
public async timeoutCallback(data) {
const remind = this.reminds.findOne({
id: data.id
});
@ -147,7 +149,7 @@ export default class extends Module {
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
text: acct(friend.doc.user) + ' ' + serifs.reminder.notify(friend.name)
});
} catch (err) {
} catch (err: any) {
// renote対象が消されていたらリマインダー解除
if (err.statusCode === 400) {
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);

View file

@ -11,6 +11,7 @@ import * as Reversi from './engine.js';
import config from '@/config.js';
import serifs from '@/serifs.js';
import type { User } from '@/misskey/user.js';
import { Note } from '@/misskey/note.js';
function getUserName(user) {
return user.name || user.username;
@ -24,11 +25,35 @@ const titles = [
];
class Session {
private account: User;
private maybeAccount?: User;
private game: any;
private form: any;
private engine: Reversi.Game;
private botColor: Reversi.Color;
private maybeEngine?: Reversi.Game;
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[] = [];
@ -100,7 +125,7 @@ class Session {
private onInit = (msg: any) => {
this.game = msg.game;
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,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
@ -198,7 +223,7 @@ class Session {
//#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) {
this.think();
@ -454,7 +479,7 @@ class Session {
try {
const res = await got.post(`${config.host}/api/notes/create`, {
json: body
}).json();
}).json<{ createdNote: Note }>();
return res.createdNote;
} catch (e) {

View file

@ -1,24 +1,34 @@
import { bindThis } from '@/decorators.js';
import Module from '@/module.js';
import Module, { InstalledModule } from '@/module.js';
import serifs from '@/serifs.js';
import config from '@/config.js';
import from '@/ai.js';
export default class extends Module {
public readonly name = 'server';
@bindThis
public install(ai: ) {
if (config.serverMonitoring) {
new Installed(this, ai);
}
return {};
}
}
class Installed extends InstalledModule {
private connection?: any;
private recentStat: any;
private warned = false;
private lastWarnedAt: number;
private lastWarnedAt?: number;
/**
* 11
*/
private statsLogs: any[] = [];
@bindThis
public install() {
if (!config.serverMonitoring) return {};
constructor(module: Module, ai: ) {
super(module, ai);
this.connection = this.ai.connection.useSharedConnection('serverStats');
this.connection.on('stats', this.onStats);
@ -31,8 +41,6 @@ export default class extends Module {
setInterval(() => {
this.check();
}, 3000);
return {};
}
@bindThis

View file

@ -1,9 +1,17 @@
import chalk from 'chalk';
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 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 {