From fd50dc790f863cef3bbbb5ce825824c50d1fda77 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 21 Jan 2024 13:27:02 +0900 Subject: [PATCH] :v: --- package.json | 44 +++-- src/ai.ts | 71 ++++---- src/config.ts | 2 +- src/decorators.ts | 41 +++++ src/friend.ts | 30 ++-- src/index.ts | 68 ++++---- src/message.ts | 26 +-- src/module.ts | 16 +- src/modules/birthday/index.ts | 12 +- src/modules/chart/index.ts | 22 +-- src/modules/check-custom-emojis/index.ts | 24 +-- src/modules/core/index.ts | 26 +-- src/modules/dice/index.ts | 12 +- src/modules/emoji-react/index.ts | 18 +- src/modules/emoji/index.ts | 12 +- src/modules/follow/index.ts | 10 +- src/modules/fortune/index.ts | 16 +- src/modules/guessing-game/index.ts | 16 +- src/modules/kazutori/index.ts | 24 +-- src/modules/keyword/index.ts | 16 +- src/modules/maze/gen-maze.ts | 2 +- src/modules/maze/index.ts | 20 +-- src/modules/maze/render-maze.ts | 4 +- src/modules/noting/index.ts | 14 +- src/modules/ping/index.ts | 10 +- src/modules/poll/index.ts | 22 +-- src/modules/reminder/index.ts | 22 +-- src/modules/reversi/back.ts | 118 +++++++------ src/modules/reversi/engine.ts | 212 +++++++++++++++++++++++ src/modules/reversi/index.ts | 65 +++---- src/modules/server/index.ts | 16 +- src/modules/sleep-report/index.ts | 10 +- src/modules/talk/index.ts | 44 ++--- src/modules/timer/index.ts | 14 +- src/modules/valentine/index.ts | 12 +- src/modules/welcome/index.ts | 8 +- src/stream.ts | 48 ++--- src/utils/includes.ts | 2 +- src/utils/log.ts | 2 +- src/utils/or.ts | 2 +- src/utils/sleep.ts | 7 + src/vocabulary.ts | 2 +- test/__modules__/test.ts | 8 +- tsconfig.json | 54 ++++-- 44 files changed, 758 insertions(+), 466 deletions(-) create mode 100644 src/decorators.ts create mode 100644 src/modules/reversi/engine.ts create mode 100644 src/utils/sleep.ts diff --git a/package.json b/package.json index 632b9fb..2fdeb73 100644 --- a/package.json +++ b/package.json @@ -1,48 +1,46 @@ { - "_v": "1.5.0", + "_v": "2.0.0", + "type": "module", "main": "./built/index.js", "scripts": { "start": "node ./built", - "build": "tsc", + "build": "tspc", "test": "jest" }, "dependencies": { "@types/chalk": "2.2.0", - "@types/lokijs": "1.5.4", - "@types/node": "16.0.1", - "@types/promise-retry": "1.1.3", - "@types/random-seed": "0.3.3", - "@types/request-promise-native": "1.0.18", - "@types/seedrandom": "2.4.28", - "@types/twemoji-parser": "13.1.1", - "@types/uuid": "8.3.1", - "@types/ws": "7.4.6", - "autobind-decorator": "2.4.0", - "canvas": "2.10.2", - "chalk": "4.1.1", + "@types/lokijs": "1.5.14", + "@types/node": "20.11.5", + "@types/promise-retry": "1.1.6", + "@types/random-seed": "0.3.5", + "@types/seedrandom": "3.0.8", + "@types/twemoji-parser": "13.1.4", + "@types/uuid": "9.0.7", + "@types/ws": "8.5.10", + "canvas": "2.11.2", + "chalk": "5.3.0", + "got": "14.0.0", "lokijs": "1.5.12", "memory-streams": "0.1.3", "misskey-reversi": "0.0.5", - "module-alias": "2.2.2", "promise-retry": "2.0.1", "random-seed": "0.3.0", "reconnecting-websocket": "4.4.0", "request": "2.88.2", - "request-promise-native": "1.0.9", "seedrandom": "3.0.5", - "timeout-as-promise": "1.0.0", - "ts-node": "10.0.0", - "twemoji-parser": "13.1.0", - "typescript": "4.3.5", - "uuid": "8.3.2", - "ws": "7.5.2" + "ts-patch": "3.1.2", + "twemoji-parser": "14.0.0", + "typescript": "5.3.3", + "typescript-transform-paths": "3.4.6", + "uuid": "9.0.1", + "ws": "8.16.0" }, "devDependencies": { "@koa/router": "9.4.0", "@types/jest": "26.0.23", "@types/koa": "2.13.1", "@types/koa__router": "8.0.4", - "@types/websocket": "1.0.2", + "@types/websocket": "1.0.10", "jest": "26.6.3", "koa": "2.13.1", "koa-json-body": "5.3.0", diff --git a/src/ai.ts b/src/ai.ts index 94a11fe..08502e2 100644 --- a/src/ai.ts +++ b/src/ai.ts @@ -1,21 +1,21 @@ // AI CORE import * as fs from 'fs'; -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import * as request from 'request-promise-native'; -import * as chalk from 'chalk'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import got from 'got'; +import chalk from 'chalk'; import { v4 as uuid } from 'uuid'; -const delay = require('timeout-as-promise'); -import config from '@/config'; -import Module from '@/module'; -import Message from '@/message'; -import Friend, { FriendDoc } from '@/friend'; -import { User } from '@/misskey/user'; -import Stream from '@/stream'; -import log from '@/utils/log'; -const pkg = require('../package.json'); +import config from '@/config.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import Friend, { FriendDoc } from '@/friend.js'; +import { User } from '@/misskey/user.js'; +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' }; type MentionHook = (msg: Message) => Promise; type ContextHook = (key: any, msg: Message, data?: any) => Promise; @@ -103,12 +103,12 @@ export default class 藍 { }); } - @autobind + @bindThis public log(msg: string) { log(chalk`[{magenta AiOS}]: ${msg}`); } - @autobind + @bindThis private run() { //#region Init DB this.meta = this.getCollection('meta', {}); @@ -207,7 +207,7 @@ export default class 藍 { * ユーザーから話しかけられたとき * (メンション、リプライ、トークのメッセージ) */ - @autobind + @bindThis private async onReceiveMessage(msg: Message): Promise { this.log(chalk.gray(`<<< An message received: ${chalk.underline(msg.id)}`)); @@ -262,7 +262,7 @@ export default class 藍 { //#endregion if (!immediate) { - await delay(1000); + await sleep(1000); } // リアクションする @@ -274,7 +274,7 @@ export default class 藍 { } } - @autobind + @bindThis private onNotification(notification: any) { switch (notification.type) { // リアクションされたら親愛度を少し上げる @@ -290,7 +290,7 @@ export default class 藍 { } } - @autobind + @bindThis private crawleTimer() { const timers = this.timers.find(); for (const timer of timers) { @@ -303,7 +303,7 @@ export default class 藍 { } } - @autobind + @bindThis private logWaking() { this.setMeta({ lastWakingAt: Date.now(), @@ -313,7 +313,7 @@ export default class 藍 { /** * データベースのコレクションを取得します */ - @autobind + @bindThis public getCollection(name: string, opts?: any): loki.Collection { let collection: loki.Collection; @@ -326,7 +326,7 @@ export default class 藍 { return collection; } - @autobind + @bindThis public lookupFriend(userId: User['id']): Friend | null { const doc = this.friends.findOne({ userId: userId @@ -342,9 +342,9 @@ export default class 藍 { /** * ファイルをドライブにアップロードします */ - @autobind + @bindThis public async upload(file: Buffer | fs.ReadStream, meta: any) { - const res = await request.post({ + const res = await got.post({ url: `${config.apiUrl}/drive/files/create`, formData: { i: config.i, @@ -354,14 +354,14 @@ export default class 藍 { } }, json: true - }); + }).json(); return res; } /** * 投稿します */ - @autobind + @bindThis public async post(param: any) { const res = await this.api('notes/create', param); return res.createdNote; @@ -370,7 +370,7 @@ export default class 藍 { /** * 指定ユーザーにトークメッセージを送信します */ - @autobind + @bindThis public sendMessage(userId: any, param: any) { return this.post(Object.assign({ visibility: 'specified', @@ -381,13 +381,14 @@ export default class 藍 { /** * APIを呼び出します */ - @autobind + @bindThis public api(endpoint: string, param?: any) { - return request.post(`${config.apiUrl}/${endpoint}`, { + this.log(`API: ${endpoint}`); + return got.post(`${config.apiUrl}/${endpoint}`, { json: Object.assign({ i: config.i }, param) - }); + }).json(); }; /** @@ -397,7 +398,7 @@ export default class 藍 { * @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID * @param data コンテキストに保存するオプションのデータ */ - @autobind + @bindThis public subscribeReply(module: Module, key: string | null, id: string, data?: any) { this.contexts.insertOne({ noteId: id, @@ -412,7 +413,7 @@ export default class 藍 { * @param module 解除するモジュール名 * @param key コンテキストを識別するためのキー */ - @autobind + @bindThis public unsubscribeReply(module: Module, key: string | null) { this.contexts.findAndRemove({ key: key, @@ -427,7 +428,7 @@ export default class 藍 { * @param delay ミリ秒 * @param data オプションのデータ */ - @autobind + @bindThis public setTimeoutWithPersistence(module: Module, delay: number, data?: any) { const id = uuid(); this.timers.insertOne({ @@ -441,7 +442,7 @@ export default class 藍 { this.log(`Timer persisted: ${module.name} ${id} ${delay}ms`); } - @autobind + @bindThis public getMeta() { const rec = this.meta.findOne(); @@ -457,7 +458,7 @@ export default class 藍 { } } - @autobind + @bindThis public setMeta(meta: Partial) { const rec = this.getMeta(); diff --git a/src/config.ts b/src/config.ts index 59154a3..9466b0c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,7 +17,7 @@ type Config = { memoryDir?: string; }; -const config = require('../config.json'); +import config from '../config.json' assert { type: 'json' }; config.wsUrl = config.host.replace('http', 'ws'); config.apiUrl = config.host + '/api'; diff --git a/src/decorators.ts b/src/decorators.ts new file mode 100644 index 0000000..db23317 --- /dev/null +++ b/src/decorators.ts @@ -0,0 +1,41 @@ +// https://github.com/andreypopp/autobind-decorator + +/** + * Return a descriptor removing the value and returning a getter + * The getter will return a .bind version of the function + * and memoize the result against a symbol on the instance + */ +export function bindThis(target: any, key: string, descriptor: any) { + let fn = descriptor.value; + + if (typeof fn !== 'function') { + throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`); + } + + return { + configurable: true, + get() { + // eslint-disable-next-line no-prototype-builtins + if (this === target.prototype || this.hasOwnProperty(key) || + typeof fn !== 'function') { + return fn; + } + + const boundFn = fn.bind(this); + Object.defineProperty(this, key, { + configurable: true, + get() { + return boundFn; + }, + set(value) { + fn = value; + delete this[key]; + }, + }); + return boundFn; + }, + set(value: any) { + fn = value; + }, + }; +} diff --git a/src/friend.ts b/src/friend.ts index 2e0cbbc..30ddef8 100644 --- a/src/friend.ts +++ b/src/friend.ts @@ -1,9 +1,9 @@ -import autobind from 'autobind-decorator'; -import 藍 from '@/ai'; -import IModule from '@/module'; -import getDate from '@/utils/get-date'; -import { User } from '@/misskey/user'; -import { genItem } from '@/vocabulary'; +import { bindThis } from '@/decorators.js'; +import 藍 from '@/ai.js'; +import IModule from '@/module.js'; +import getDate from '@/utils/get-date.js'; +import { User } from '@/misskey/user.js'; +import { genItem } from '@/vocabulary.js'; export type FriendDoc = { userId: string; @@ -69,7 +69,7 @@ export default class Friend { } } - @autobind + @bindThis public updateUser(user: Partial) { this.doc.user = { ...this.doc.user, @@ -78,7 +78,7 @@ export default class Friend { this.save(); } - @autobind + @bindThis public getPerModulesData(module: IModule) { if (this.doc.perModulesData == null) { this.doc.perModulesData = {}; @@ -92,7 +92,7 @@ export default class Friend { return this.doc.perModulesData[module.name]; } - @autobind + @bindThis public setPerModulesData(module: IModule, data: any) { if (this.doc.perModulesData == null) { this.doc.perModulesData = {}; @@ -103,7 +103,7 @@ export default class Friend { this.save(); } - @autobind + @bindThis public incLove(amount = 1) { const today = getDate(); @@ -127,7 +127,7 @@ export default class Friend { this.ai.log(`💗 ${this.userId} +${amount}`); } - @autobind + @bindThis public decLove(amount = 1) { // 親愛度MAXなら下げない if (this.doc.love === 100) return; @@ -148,18 +148,18 @@ export default class Friend { this.ai.log(`💢 ${this.userId} -${amount}`); } - @autobind + @bindThis public updateName(name: string) { this.doc.name = name; this.save(); } - @autobind + @bindThis public save() { this.ai.friends.update(this.doc); } - @autobind + @bindThis public generateTransferCode(): string { const code = genItem(); @@ -169,7 +169,7 @@ export default class Friend { return code; } - @autobind + @bindThis public transferMemory(code: string): boolean { const src = this.ai.friends.findOne({ transferCode: code diff --git a/src/index.ts b/src/index.ts index c6c2fa8..e94b9a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,40 +1,38 @@ // AiOS bootstrapper -import 'module-alias/register'; +import chalk from 'chalk'; +import got from 'got'; +import promiseRetry from 'promise-retry'; -import * as chalk from 'chalk'; -import * as request from 'request-promise-native'; -const promiseRetry = require('promise-retry'); +import 藍 from './ai.js'; +import config from './config.js'; +import _log from './utils/log.js'; +import pkg from '../package.json' assert { type: 'json' }; -import 藍 from './ai'; -import config from './config'; -import _log from './utils/log'; -const pkg = require('../package.json'); - -import CoreModule from './modules/core'; -import TalkModule from './modules/talk'; -import BirthdayModule from './modules/birthday'; -import ReversiModule from './modules/reversi'; -import PingModule from './modules/ping'; -import EmojiModule from './modules/emoji'; -import EmojiReactModule from './modules/emoji-react'; -import FortuneModule from './modules/fortune'; -import GuessingGameModule from './modules/guessing-game'; -import KazutoriModule from './modules/kazutori'; -import KeywordModule from './modules/keyword'; -import WelcomeModule from './modules/welcome'; -import TimerModule from './modules/timer'; -import DiceModule from './modules/dice'; -import ServerModule from './modules/server'; -import FollowModule from './modules/follow'; -import ValentineModule from './modules/valentine'; -import MazeModule from './modules/maze'; -import ChartModule from './modules/chart'; -import SleepReportModule from './modules/sleep-report'; -import NotingModule from './modules/noting'; -import PollModule from './modules/poll'; -import ReminderModule from './modules/reminder'; -import CheckCustomEmojisModule from './modules/check-custom-emojis'; +import CoreModule from './modules/core/index.js'; +import TalkModule from './modules/talk/index.js'; +import BirthdayModule from './modules/birthday/index.js'; +import ReversiModule from './modules/reversi/index.js'; +import PingModule from './modules/ping/index.js'; +import EmojiModule from './modules/emoji/index.js'; +import EmojiReactModule from './modules/emoji-react/index.js'; +import FortuneModule from './modules/fortune/index.js'; +import GuessingGameModule from './modules/guessing-game/index.js'; +import KazutoriModule from './modules/kazutori/index.js'; +import KeywordModule from './modules/keyword/index.js'; +import WelcomeModule from './modules/welcome/index.js'; +import TimerModule from './modules/timer/index.js'; +import DiceModule from './modules/dice/index.js'; +import ServerModule from './modules/server/index.js'; +import FollowModule from './modules/follow/index.js'; +import ValentineModule from './modules/valentine/index.js'; +import MazeModule from './modules/maze/index.js'; +import ChartModule from './modules/chart/index.js'; +import SleepReportModule from './modules/sleep-report/index.js'; +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'; console.log(' __ ____ _____ ___ '); console.log(' /__\\ (_ _)( _ )/ __)'); @@ -51,11 +49,11 @@ promiseRetry(retry => { log(`Account fetching... ${chalk.gray(config.host)}`); // アカウントをフェッチ - return request.post(`${config.apiUrl}/i`, { + return got.post(`${config.apiUrl}/i`, { json: { i: config.i } - }).catch(retry); + }).json().catch(retry); }, { retries: 3 }).then(account => { diff --git a/src/message.ts b/src/message.ts index a50043c..c662e8a 100644 --- a/src/message.ts +++ b/src/message.ts @@ -1,13 +1,13 @@ -import autobind from 'autobind-decorator'; -import * as chalk from 'chalk'; -const delay = require('timeout-as-promise'); +import { bindThis } from '@/decorators.js'; +import chalk from 'chalk'; -import 藍 from '@/ai'; -import Friend from '@/friend'; -import { User } from '@/misskey/user'; -import includes from '@/utils/includes'; -import or from '@/utils/or'; -import config from '@/config'; +import 藍 from '@/ai.js'; +import Friend from '@/friend.js'; +import { User } from '@/misskey/user.js'; +import includes from '@/utils/includes.js'; +import or from '@/utils/or.js'; +import config from '@/config.js'; +import { sleep } from '@/utils/sleep.js'; export default class Message { private ai: 藍; @@ -68,7 +68,7 @@ export default class Message { }); } - @autobind + @bindThis public async reply(text: string | null, opts?: { file?: any; cw?: string; @@ -80,7 +80,7 @@ export default class Message { this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`); if (!opts?.immediate) { - await delay(2000); + await sleep(2000); } return await this.ai.post({ @@ -92,12 +92,12 @@ export default class Message { }); } - @autobind + @bindThis public includes(words: string[]): boolean { return includes(this.text, words); } - @autobind + @bindThis public or(words: (string | RegExp)[]): boolean { return or(this.text, words); } diff --git a/src/module.ts b/src/module.ts index 061e1bf..27bcf65 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,5 +1,5 @@ -import autobind from 'autobind-decorator'; -import 藍, { InstallerResult } from '@/ai'; +import { bindThis } from '@/decorators.js'; +import 藍, { InstallerResult } from '@/ai.js'; export default abstract class Module { public abstract readonly name: string; @@ -24,7 +24,7 @@ export default abstract class Module { public abstract install(): InstallerResult; - @autobind + @bindThis protected log(msg: string) { this.ai.log(`[${this.name}]: ${msg}`); } @@ -35,7 +35,7 @@ export default abstract class Module { * @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID * @param data コンテキストに保存するオプションのデータ */ - @autobind + @bindThis protected subscribeReply(key: string | null, id: string, data?: any) { this.ai.subscribeReply(this, key, id, data); } @@ -44,7 +44,7 @@ export default abstract class Module { * 返信の待ち受けを解除します * @param key コンテキストを識別するためのキー */ - @autobind + @bindThis protected unsubscribeReply(key: string | null) { this.ai.unsubscribeReply(this, key); } @@ -55,17 +55,17 @@ export default abstract class Module { * @param delay ミリ秒 * @param data オプションのデータ */ - @autobind + @bindThis public setTimeoutWithPersistence(delay: number, data?: any) { this.ai.setTimeoutWithPersistence(this, delay, data); } - @autobind + @bindThis protected getData() { return this.doc.data; } - @autobind + @bindThis protected setData(data: any) { this.doc.data = data; this.ai.moduleData.update(this.doc); diff --git a/src/modules/birthday/index.ts b/src/modules/birthday/index.ts index 2a788a7..a23a401 100644 --- a/src/modules/birthday/index.ts +++ b/src/modules/birthday/index.ts @@ -1,7 +1,7 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Friend from '@/friend'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Friend from '@/friend.js'; +import serifs from '@/serifs.js'; function zeroPadding(num: number, length: number): string { return ('0000000000' + num).slice(-length); @@ -10,7 +10,7 @@ function zeroPadding(num: number, length: number): string { export default class extends Module { public readonly name = 'birthday'; - @autobind + @bindThis public install() { this.crawleBirthday(); setInterval(this.crawleBirthday, 1000 * 60 * 3); @@ -21,7 +21,7 @@ export default class extends Module { /** * 誕生日のユーザーがいないかチェック(いたら祝う) */ - @autobind + @bindThis private crawleBirthday() { const now = new Date(); const m = now.getMonth(); diff --git a/src/modules/chart/index.ts b/src/modules/chart/index.ts index ead3721..7605707 100644 --- a/src/modules/chart/index.ts +++ b/src/modules/chart/index.ts @@ -1,15 +1,15 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; -import Message from '@/message'; -import { renderChart } from './render-chart'; -import { items } from '@/vocabulary'; -import config from '@/config'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import Message from '@/message.js'; +import { renderChart } from './render-chart.js'; +import { items } from '@/vocabulary.js'; +import config from '@/config.js'; export default class extends Module { public readonly name = 'chart'; - @autobind + @bindThis public install() { if (config.chartEnabled === false) return {}; @@ -21,7 +21,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async post() { const now = new Date(); if (now.getHours() !== 23) return; @@ -41,7 +41,7 @@ export default class extends Module { }); } - @autobind + @bindThis private async genChart(type, params?): Promise { this.log('Chart data fetching...'); @@ -134,7 +134,7 @@ export default class extends Module { return file; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.includes(['チャート'])) { return false; diff --git a/src/modules/check-custom-emojis/index.ts b/src/modules/check-custom-emojis/index.ts index d164c59..658a983 100644 --- a/src/modules/check-custom-emojis/index.ts +++ b/src/modules/check-custom-emojis/index.ts @@ -1,9 +1,9 @@ -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import Module from '@/module'; -import serifs from '@/serifs'; -import config from '@/config'; -import Message from '@/message'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import config from '@/config.js'; +import Message from '@/message.js'; export default class extends Module { public readonly name = 'checkCustomEmojis'; @@ -13,7 +13,7 @@ export default class extends Module { updatedAt: number; }>; - @autobind + @bindThis public install() { if (!config.checkEmojisEnabled) return {}; this.lastEmoji = this.ai.getCollection('lastEmoji', { @@ -28,7 +28,7 @@ export default class extends Module { }; } - @autobind + @bindThis private timeCheck() { const now = new Date(); if (now.getHours() !== 23) return; @@ -42,7 +42,7 @@ export default class extends Module { this.post(); } - @autobind + @bindThis private async post() { this.log('Start to Check CustomEmojis.'); const lastEmoji = this.lastEmoji.find({}); @@ -99,7 +99,7 @@ export default class extends Module { this.log('Check CustomEmojis finished!'); } - @autobind + @bindThis private async checkCumstomEmojis(lastId : any) { this.log('CustomEmojis fetching...'); let emojisData; @@ -140,7 +140,7 @@ export default class extends Module { return emojisData; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.includes(['カスタムえもじチェック','カスタムえもじを調べて','カスタムえもじを確認'])) { return false; @@ -155,7 +155,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async sleep(ms: number) { return new Promise((res) => setTimeout(res, ms)); } diff --git a/src/modules/core/index.ts b/src/modules/core/index.ts index 68fb47a..feafdac 100644 --- a/src/modules/core/index.ts +++ b/src/modules/core/index.ts @@ -1,15 +1,15 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; -import { safeForInterpolate } from '@/utils/safe-for-interpolate'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; +import { safeForInterpolate } from '@/utils/safe-for-interpolate.js'; const titles = ['さん', 'くん', '君', 'ちゃん', '様', '先生']; export default class extends Module { public readonly name = 'core'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook, @@ -17,7 +17,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.text) return false; @@ -30,7 +30,7 @@ export default class extends Module { ); } - @autobind + @bindThis private transferBegin(msg: Message): boolean { if (!msg.text) return false; if (!msg.includes(['引継', '引き継ぎ', '引越', '引っ越し'])) return false; @@ -42,7 +42,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private transferEnd(msg: Message): boolean { if (!msg.text) return false; if (!msg.text.startsWith('「') || !msg.text.endsWith('」')) return false; @@ -60,7 +60,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private setName(msg: Message): boolean { if (!msg.text) return false; if (!msg.text.includes('って呼んで')) return false; @@ -94,7 +94,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private modules(msg: Message): boolean { if (!msg.text) return false; if (!msg.or(['modules'])) return false; @@ -114,7 +114,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private version(msg: Message): boolean { if (!msg.text) return false; if (!msg.or(['v', 'version', 'バージョン'])) return false; @@ -126,7 +126,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private async contextHook(key: any, msg: Message, data: any) { if (msg.text == null) return; diff --git a/src/modules/dice/index.ts b/src/modules/dice/index.ts index 646ac01..3f42eae 100644 --- a/src/modules/dice/index.ts +++ b/src/modules/dice/index.ts @@ -1,19 +1,19 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; export default class extends Module { public readonly name = 'dice'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.text == null) return false; diff --git a/src/modules/emoji-react/index.ts b/src/modules/emoji-react/index.ts index e671b4d..145ec65 100644 --- a/src/modules/emoji-react/index.ts +++ b/src/modules/emoji-react/index.ts @@ -1,18 +1,18 @@ -import autobind from 'autobind-decorator'; +import { bindThis } from '@/decorators.js'; import { parse } from 'twemoji-parser'; -const delay = require('timeout-as-promise'); -import { Note } from '@/misskey/note'; -import Module from '@/module'; -import Stream from '@/stream'; -import includes from '@/utils/includes'; +import { 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; - @autobind + @bindThis public install() { this.htl = this.ai.connection.useSharedConnection('homeTimeline'); this.htl.on('note', this.onNote); @@ -20,7 +20,7 @@ export default class extends Module { return {}; } - @autobind + @bindThis private async onNote(note: Note) { if (note.reply != null) return; if (note.text == null) return; @@ -28,7 +28,7 @@ export default class extends Module { const react = async (reaction: string, immediate = false) => { if (!immediate) { - await delay(1500); + await sleep(1500); } this.ai.api('notes/reactions/create', { noteId: note.id, diff --git a/src/modules/emoji/index.ts b/src/modules/emoji/index.ts index 3dbd5b8..0f8b00c 100644 --- a/src/modules/emoji/index.ts +++ b/src/modules/emoji/index.ts @@ -1,7 +1,7 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; const hands = [ '👏', @@ -129,14 +129,14 @@ const faces = [ export default class extends Module { public readonly name = 'emoji'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.includes(['顔文字', '絵文字', 'emoji', '福笑い'])) { const hand = hands[Math.floor(Math.random() * hands.length)]; diff --git a/src/modules/follow/index.ts b/src/modules/follow/index.ts index 0f52047..dcf369e 100644 --- a/src/modules/follow/index.ts +++ b/src/modules/follow/index.ts @@ -1,18 +1,18 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; export default class extends Module { public readonly name = 'follow'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.text && msg.includes(['フォロー', 'フォロバ', 'follow me'])) { if (!msg.user.isFollowing) { diff --git a/src/modules/fortune/index.ts b/src/modules/fortune/index.ts index 8a46535..c9692fa 100644 --- a/src/modules/fortune/index.ts +++ b/src/modules/fortune/index.ts @@ -1,9 +1,9 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; -import * as seedrandom from 'seedrandom'; -import { genItem } from '@/vocabulary'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; +import seedrandom from 'seedrandom'; +import { genItem } from '@/vocabulary.js'; export const blessing = [ '藍吉', @@ -40,14 +40,14 @@ export const blessing = [ export default class extends Module { public readonly name = 'fortune'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.includes(['占', 'うらな', '運勢', 'おみくじ'])) { const date = new Date(); diff --git a/src/modules/guessing-game/index.ts b/src/modules/guessing-game/index.ts index b9728a0..93ad457 100644 --- a/src/modules/guessing-game/index.ts +++ b/src/modules/guessing-game/index.ts @@ -1,8 +1,8 @@ -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; export default class extends Module { public readonly name = 'guessingGame'; @@ -16,7 +16,7 @@ export default class extends Module { endedAt: number | null; }>; - @autobind + @bindThis public install() { this.guesses = this.ai.getCollection('guessingGame', { indices: ['userId'] @@ -28,7 +28,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.includes(['数当て', '数あて'])) return false; @@ -55,7 +55,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private async contextHook(key: any, msg: Message) { if (msg.text == null) return; diff --git a/src/modules/kazutori/index.ts b/src/modules/kazutori/index.ts index ad3a09a..a5a3caa 100644 --- a/src/modules/kazutori/index.ts +++ b/src/modules/kazutori/index.ts @@ -1,10 +1,10 @@ -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; -import { User } from '@/misskey/user'; -import { acct } from '@/utils/acct'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; +import { User } from '@/misskey/user.js'; +import { acct } from '@/utils/acct.js'; type Game = { votes: { @@ -27,7 +27,7 @@ export default class extends Module { private games: loki.Collection; - @autobind + @bindThis public install() { this.games = this.ai.getCollection('kazutori'); @@ -40,7 +40,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.includes(['数取り'])) return false; @@ -82,7 +82,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private async contextHook(key: any, msg: Message) { if (msg.text == null) return { reaction: 'hmm' @@ -139,7 +139,7 @@ export default class extends Module { /** * 終了すべきゲームがないかチェック */ - @autobind + @bindThis private crawleGameEnd() { const game = this.games.findOne({ isEnded: false @@ -156,7 +156,7 @@ export default class extends Module { /** * ゲームを終わらせる */ - @autobind + @bindThis private finish(game: Game) { game.isEnded = true; this.games.update(game); diff --git a/src/modules/keyword/index.ts b/src/modules/keyword/index.ts index 7eeac02..31f7b0f 100644 --- a/src/modules/keyword/index.ts +++ b/src/modules/keyword/index.ts @@ -1,9 +1,9 @@ -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import Module from '@/module'; -import config from '@/config'; -import serifs from '@/serifs'; -import { mecab } from './mecab'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import Module from '@/module.js'; +import config from '@/config.js'; +import serifs from '@/serifs.js'; +import { mecab } from './mecab.js'; function kanaToHira(str: string) { return str.replace(/[\u30a1-\u30f6]/g, match => { @@ -20,7 +20,7 @@ export default class extends Module { learnedAt: number; }>; - @autobind + @bindThis public install() { if (!config.keywordEnabled) return {}; @@ -33,7 +33,7 @@ export default class extends Module { return {}; } - @autobind + @bindThis private async learn() { const tl = await this.ai.api('notes/local-timeline', { limit: 30 diff --git a/src/modules/maze/gen-maze.ts b/src/modules/maze/gen-maze.ts index a14e4ee..050f0ad 100644 --- a/src/modules/maze/gen-maze.ts +++ b/src/modules/maze/gen-maze.ts @@ -1,5 +1,5 @@ import * as gen from 'random-seed'; -import { CellType } from './maze'; +import { CellType } from './maze.js'; const cellVariants = { void: { diff --git a/src/modules/maze/index.ts b/src/modules/maze/index.ts index 8311e94..40f67ae 100644 --- a/src/modules/maze/index.ts +++ b/src/modules/maze/index.ts @@ -1,14 +1,14 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; -import { genMaze } from './gen-maze'; -import { renderMaze } from './render-maze'; -import Message from '@/message'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import { genMaze } from './gen-maze.js'; +import { renderMaze } from './render-maze.js'; +import Message from '@/message.js'; export default class extends Module { public readonly name = 'maze'; - @autobind + @bindThis public install() { this.post(); setInterval(this.post, 1000 * 60 * 3); @@ -18,7 +18,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async post() { const now = new Date(); if (now.getHours() !== 22) return; @@ -38,7 +38,7 @@ export default class extends Module { }); } - @autobind + @bindThis private async genMazeFile(seed, size?): Promise { this.log('Maze generating...'); const maze = genMaze(seed, size); @@ -55,7 +55,7 @@ export default class extends Module { return file; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.includes(['迷路'])) { let size: string | null = null; diff --git a/src/modules/maze/render-maze.ts b/src/modules/maze/render-maze.ts index 6f4f5da..9e2b064 100644 --- a/src/modules/maze/render-maze.ts +++ b/src/modules/maze/render-maze.ts @@ -1,8 +1,8 @@ import * as gen from 'random-seed'; import { createCanvas } from 'canvas'; -import { CellType } from './maze'; -import { themes } from './themes'; +import { CellType } from './maze.js'; +import { themes } from './themes.js'; const imageSize = 4096; // px const margin = 96 * 4; diff --git a/src/modules/noting/index.ts b/src/modules/noting/index.ts index c695504..6d38bce 100644 --- a/src/modules/noting/index.ts +++ b/src/modules/noting/index.ts @@ -1,13 +1,13 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; -import { genItem } from '@/vocabulary'; -import config from '@/config'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import { genItem } from '@/vocabulary.js'; +import config from '@/config.js'; export default class extends Module { public readonly name = 'noting'; - @autobind + @bindThis public install() { if (config.notingEnabled === false) return {}; @@ -20,7 +20,7 @@ export default class extends Module { return {}; } - @autobind + @bindThis private post() { const notes = [ ...serifs.noting.notes, diff --git a/src/modules/ping/index.ts b/src/modules/ping/index.ts index 147a7d5..39ba6ed 100644 --- a/src/modules/ping/index.ts +++ b/src/modules/ping/index.ts @@ -1,18 +1,18 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; export default class extends Module { public readonly name = 'ping'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.text && msg.text.includes('ping')) { msg.reply('PONG!', { diff --git a/src/modules/poll/index.ts b/src/modules/poll/index.ts index a5339f5..c3bbe6e 100644 --- a/src/modules/poll/index.ts +++ b/src/modules/poll/index.ts @@ -1,15 +1,15 @@ -import autobind from 'autobind-decorator'; -import Message from '@/message'; -import Module from '@/module'; -import serifs from '@/serifs'; -import { genItem } from '@/vocabulary'; -import config from '@/config'; -import { Note } from '@/misskey/note'; +import { bindThis } from '@/decorators.js'; +import Message from '@/message.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import { genItem } from '@/vocabulary.js'; +import config from '@/config.js'; +import { Note } from '@/misskey/note.js'; export default class extends Module { public readonly name = 'poll'; - @autobind + @bindThis public install() { setInterval(() => { if (Math.random() < 0.1) { @@ -23,7 +23,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async post() { const duration = 1000 * 60 * 15; @@ -89,7 +89,7 @@ export default class extends Module { }); } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.or(['/poll']) || msg.user.username !== config.master) { return false; @@ -102,7 +102,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private async timeoutCallback({ title, noteId }) { const note: Note = await this.ai.api('notes/show', { noteId }); diff --git a/src/modules/reminder/index.ts b/src/modules/reminder/index.ts index 0e66942..0842edc 100644 --- a/src/modules/reminder/index.ts +++ b/src/modules/reminder/index.ts @@ -1,10 +1,10 @@ -import autobind from 'autobind-decorator'; -import * as loki from 'lokijs'; -import Module from '@/module'; -import Message from '@/message'; -import serifs, { getSerif } from '@/serifs'; -import { acct } from '@/utils/acct'; -import config from '@/config'; +import { bindThis } from '@/decorators.js'; +import loki from 'lokijs'; +import Module 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'; const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12; @@ -20,7 +20,7 @@ export default class extends Module { createdAt: number; }>; - @autobind + @bindThis public install() { this.reminds = this.ai.getCollection('reminds', { indices: ['userId', 'id'] @@ -33,7 +33,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { let text = msg.extractedText.toLowerCase(); if (!text.startsWith('remind') && !text.startsWith('todo')) return false; @@ -98,7 +98,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async contextHook(key: any, msg: Message, data: any) { if (msg.text == null) return; @@ -128,7 +128,7 @@ export default class extends Module { } } - @autobind + @bindThis private async timeoutCallback(data) { const remind = this.reminds.findOne({ id: data.id diff --git a/src/modules/reversi/back.ts b/src/modules/reversi/back.ts index b7b9d3d..19d8fe5 100644 --- a/src/modules/reversi/back.ts +++ b/src/modules/reversi/back.ts @@ -6,13 +6,11 @@ * 切断されてしまうので、別々のプロセスで行うようにします */ -import 'module-alias/register'; - -import * as request from 'request-promise-native'; -import Reversi, { Color } from 'misskey-reversi'; -import config from '@/config'; -import serifs from '@/serifs'; -import { User } from '@/misskey/user'; +import got from 'got'; +import * as Reversi from './engine.js'; +import config from '@/config.js'; +import serifs from '@/serifs.js'; +import { User } from '@/misskey/user.js'; function getUserName(user) { return user.name || user.username; @@ -29,8 +27,10 @@ class Session { private account: User; private game: any; private form: any; - private o: Reversi; - private botColor: Color; + private engine: Reversi.Game; + private botColor: Reversi.Color; + + private appliedOps: string[] = []; /** * 隅周辺のインデックスリスト(静的評価に利用) @@ -79,7 +79,7 @@ class Session { } private get url(): string { - return `${config.host}/games/reversi/${this.game.id}`; + return `${config.host}/reversi/g/${this.game.id}`; } constructor() { @@ -89,10 +89,9 @@ class Session { private onMessage = async (msg: any) => { switch (msg.type) { case '_init_': this.onInit(msg.body); break; - case 'updateForm': this.onUpdateForn(msg.body); break; case 'started': this.onStarted(msg.body); break; case 'ended': this.onEnded(msg.body); break; - case 'set': this.onSet(msg.body); break; + case 'log': this.onLog(msg.body); break; } } @@ -103,18 +102,11 @@ class Session { this.account = msg.account; } - /** - * フォームが更新されたとき - */ - private onUpdateForn = (msg: any) => { - this.form.find(i => i.id == msg.id).value = msg.value; - } - /** * 対局が始まったとき */ private onStarted = (msg: any) => { - this.game = msg; + this.game = msg.game; // TLに投稿する this.postGameStarted().then(note => { @@ -122,24 +114,24 @@ class Session { }); // リバーシエンジン初期化 - this.o = new Reversi(this.game.map, { + this.engine = new Reversi.Game(this.game.map, { isLlotheo: this.game.isLlotheo, canPutEverywhere: this.game.canPutEverywhere, loopedBoard: this.game.loopedBoard }); - this.maxTurn = this.o.map.filter(p => p === 'empty').length - this.o.board.filter(x => x != null).length; + this.maxTurn = this.engine.map.filter(p => p === 'empty').length - this.engine.board.filter(x => x != null).length; //#region 隅の位置計算など //#region 隅 - this.o.map.forEach((pix, i) => { + this.engine.map.forEach((pix, i) => { if (pix == 'null') return; - const [x, y] = this.o.transformPosToXy(i); + const [x, y] = this.engine.posToXy(i); const get = (x, y) => { - if (x < 0 || y < 0 || x >= this.o.mapWidth || y >= this.o.mapHeight) return 'null'; - return this.o.mapDataGet(this.o.transformXyToPos(x, y)); + if (x < 0 || y < 0 || x >= this.engine.mapWidth || y >= this.engine.mapHeight) return 'null'; + return this.engine.mapDataGet(this.engine.xyToPos(x, y)); }; const isNotSumi = ( @@ -171,15 +163,15 @@ class Session { //#endregion //#region 隅の隣 - this.o.map.forEach((pix, i) => { + this.engine.map.forEach((pix, i) => { if (pix == 'null') return; if (this.sumiIndexes.includes(i)) return; - const [x, y] = this.o.transformPosToXy(i); + const [x, y] = this.engine.posToXy(i); const check = (x, y) => { - if (x < 0 || y < 0 || x >= this.o.mapWidth || y >= this.o.mapHeight) return 0; - return this.sumiIndexes.includes(this.o.transformXyToPos(x, y)); + if (x < 0 || y < 0 || x >= this.engine.mapWidth || y >= this.engine.mapHeight) return 0; + return this.sumiIndexes.includes(this.engine.xyToPos(x, y)); }; const isSumiNear = ( @@ -253,12 +245,22 @@ class Session { /** * 打たれたとき */ - private onSet = (msg: any) => { - this.o.put(msg.color, msg.pos); - this.currentTurn++; + private onLog = (log: any) => { + if (log.id == null || !this.appliedOps.includes(log.id)) { + switch (log.operation) { + case 'put': { + this.engine.putStone(log.pos); + this.currentTurn++; - if (msg.next === this.botColor) { - this.think(); + if (this.engine.turn === this.botColor) { + this.think(); + } + break; + } + + default: + break; + } } } @@ -268,10 +270,10 @@ class Session { * TODO: 接待時はまるっと処理の中身を変え、とにかく相手が隅を取っていること優先な評価にする */ private staticEval = () => { - let score = this.o.canPutSomewhere(this.botColor).length; + let score = this.engine.getPuttablePlaces(this.botColor).length; for (const index of this.sumiIndexes) { - const stone = this.o.board[index]; + const stone = this.engine.board[index]; if (stone === this.botColor) { score += 1000; // 自分が隅を取っていたらスコアプラス @@ -283,7 +285,7 @@ class Session { // TODO: ここに (隅以外の確定石の数 * 100) をスコアに加算する処理を入れる for (const index of this.sumiNearIndexes) { - const stone = this.o.board[index]; + const stone = this.engine.board[index]; if (stone === this.botColor) { score -= 10; // 自分が隅の周辺を取っていたらスコアマイナス(危険なので) @@ -319,13 +321,13 @@ class Session { */ const dive = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => { // 試し打ち - this.o.put(this.o.turn, pos); + this.engine.putStone(pos); - const isBotTurn = this.o.turn === this.botColor; + const isBotTurn = this.engine.turn === this.botColor; // 勝った - if (this.o.turn === null) { - const winner = this.o.winner; + if (this.engine.turn === null) { + const winner = this.engine.winner; // 勝つことによる基本スコア const base = 10000; @@ -334,14 +336,14 @@ class Session { if (this.game.isLlotheo) { // 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する - score = this.o.winner ? base - (this.o.blackCount * 100) : base - (this.o.whiteCount * 100); + score = this.engine.winner ? base - (this.engine.blackCount * 100) : base - (this.engine.whiteCount * 100); } else { // 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する - score = this.o.winner ? base + (this.o.blackCount * 100) : base + (this.o.whiteCount * 100); + score = this.engine.winner ? base + (this.engine.blackCount * 100) : base + (this.engine.whiteCount * 100); } // 巻き戻し - this.o.undo(); + this.engine.undo(); // 接待なら自分が負けた方が高スコア return this.isSettai @@ -354,11 +356,11 @@ class Session { const score = this.staticEval(); // 巻き戻し - this.o.undo(); + this.engine.undo(); return score; } else { - const cans = this.o.canPutSomewhere(this.o.turn); + const cans = this.engine.getPuttablePlaces(this.engine.turn); let value = isBotTurn ? -Infinity : Infinity; let a = alpha; @@ -384,24 +386,34 @@ class Session { } // 巻き戻し - this.o.undo(); + this.engine.undo(); return value; } }; - const cans = this.o.canPutSomewhere(this.botColor); + const cans = this.engine.getPuttablePlaces(this.botColor); const scores = cans.map(p => dive(p)); const pos = cans[scores.indexOf(Math.max(...scores))]; console.log('Thinked:', pos); console.timeEnd('think'); + this.engine.putStone(pos); + this.currentTurn++; + setTimeout(() => { + const id = Math.random().toString(36).slice(2); process.send!({ - type: 'put', - pos + type: 'putStone', + pos, + id }); + this.appliedOps.push(id); + + if (this.engine.turn === this.botColor) { + this.think(); + } }, 500); } @@ -433,9 +445,9 @@ class Session { } try { - const res = await request.post(`${config.host}/api/notes/create`, { + const res = await got.post(`${config.host}/api/notes/create`, { json: body - }); + }).json(); return res.createdNote; } catch (e) { diff --git a/src/modules/reversi/engine.ts b/src/modules/reversi/engine.ts new file mode 100644 index 0000000..dd4fb13 --- /dev/null +++ b/src/modules/reversi/engine.ts @@ -0,0 +1,212 @@ +/** + * true ... 黒 + * false ... 白 + */ +export type Color = boolean; +const BLACK = true; +const WHITE = false; + +export type MapCell = 'null' | 'empty'; + +export type Options = { + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; +}; + +export type Undo = { + color: Color; + pos: number; + + /** + * 反転した石の位置の配列 + */ + effects: number[]; + + turn: Color | null; +}; + +export class Game { + public map: MapCell[]; + public mapWidth: number; + public mapHeight: number; + public board: (Color | null | undefined)[]; + public turn: Color | null = BLACK; + public opts: Options; + + public prevPos = -1; + public prevColor: Color | null = null; + + private logs: Undo[] = []; + + constructor(map: string[], opts: Options) { + //#region binds + this.putStone = this.putStone.bind(this); + //#endregion + + //#region Options + this.opts = opts; + if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; + if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; + if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; + //#endregion + + //#region Parse map data + this.mapWidth = map[0].length; + this.mapHeight = map.length; + const mapData = map.join(''); + + this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); + + this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); + //#endregion + + // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある + if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; + } + + public get blackCount() { + return this.board.filter(x => x === BLACK).length; + } + + public get whiteCount() { + return this.board.filter(x => x === WHITE).length; + } + + public posToXy(pos: number): number[] { + const x = pos % this.mapWidth; + const y = Math.floor(pos / this.mapWidth); + return [x, y]; + } + + public xyToPos(x: number, y: number): number { + return x + (y * this.mapWidth); + } + + public putStone(pos: number) { + const color = this.turn; + if (color == null) return; + + this.prevPos = pos; + this.prevColor = color; + + this.board[pos] = color; + + // 反転させられる石を取得 + const effects = this.effects(color, pos); + + // 反転させる + for (const pos of effects) { + this.board[pos] = color; + } + + const turn = this.turn; + + this.logs.push({ + color, + pos, + effects, + turn, + }); + + this.calcTurn(); + } + + private calcTurn() { + // ターン計算 + this.turn = + this.canPutSomewhere(!this.prevColor) ? !this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : + null; + } + + public undo() { + const undo = this.logs.pop()!; + this.prevColor = undo.color; + this.prevPos = undo.pos; + this.board[undo.pos] = null; + for (const pos of undo.effects) { + const color = this.board[pos]; + this.board[pos] = !color; + } + this.turn = undo.turn; + } + + public mapDataGet(pos: number): MapCell { + const [x, y] = this.posToXy(pos); + return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; + } + + public getPuttablePlaces(color: Color): number[] { + return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); + } + + public canPutSomewhere(color: Color): boolean { + return this.getPuttablePlaces(color).length > 0; + } + + public canPut(color: Color, pos: number): boolean { + return ( + this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない + this.opts.canPutEverywhere ? this.mapDataGet(pos) === 'empty' : // 挟んでなくても置けるモード + this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか + } + + /** + * 指定のマスに石を置いた時の、反転させられる石を取得します + * @param color 自分の色 + * @param initPos 位置 + */ + public effects(color: Color, initPos: number): number[] { + const enemyColor = !color; + + const diffVectors: [number, number][] = [ + [0, -1], // 上 + [+1, -1], // 右上 + [+1, 0], // 右 + [+1, +1], // 右下 + [0, +1], // 下 + [-1, +1], // 左下 + [-1, 0], // 左 + [-1, -1], // 左上 + ]; + + const effectsInLine = ([dx, dy]: [number, number]): number[] => { + const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; + + const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 + let [x, y] = this.posToXy(initPos); + while (true) { + [x, y] = nextPos(x, y); + + // 座標が指し示す位置がボード外に出たとき + if (this.opts.loopedBoard && this.xyToPos( + (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), + (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) { + // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) + return found; + } else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) return []; // 挟めないことが確定 (盤面外に到達) + + const pos = this.xyToPos(x, y); + if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) + const stone = this.board[pos]; + if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) + if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) + if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) + } + }; + + return ([] as number[]).concat(...diffVectors.map(effectsInLine)); + } + + public get isEnded(): boolean { + return this.turn === null; + } + + public get winner(): Color | null { + return this.isEnded ? + this.blackCount === this.whiteCount ? null : + this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : + undefined as never; + } +} diff --git a/src/modules/reversi/index.ts b/src/modules/reversi/index.ts index 82b5768..2838fd8 100644 --- a/src/modules/reversi/index.ts +++ b/src/modules/reversi/index.ts @@ -1,11 +1,16 @@ import * as childProcess from 'child_process'; -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; -import config from '@/config'; -import Message from '@/message'; -import Friend from '@/friend'; -import getDate from '@/utils/get-date'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import config from '@/config.js'; +import Message from '@/message.js'; +import Friend from '@/friend.js'; +import getDate from '@/utils/get-date.js'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); export default class extends Module { public readonly name = 'reversi'; @@ -15,17 +20,17 @@ export default class extends Module { */ private reversiConnection?: any; - @autobind + @bindThis public install() { if (!config.reversiEnabled) return {}; - this.reversiConnection = this.ai.connection.useSharedConnection('gamesReversi'); + this.reversiConnection = this.ai.connection.useSharedConnection('reversi'); // 招待されたとき - this.reversiConnection.on('invited', msg => this.onReversiInviteMe(msg.parent)); + this.reversiConnection.on('invited', msg => this.onReversiInviteMe(msg.user)); // マッチしたとき - this.reversiConnection.on('matched', msg => this.onReversiGameStart(msg)); + this.reversiConnection.on('matched', msg => this.onReversiGameStart(msg.game)); if (config.reversiEnabled) { const mainStream = this.ai.connection.useSharedConnection('main'); @@ -43,13 +48,13 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.includes(['リバーシ', 'オセロ', 'reversi', 'othello'])) { if (config.reversiEnabled) { msg.reply(serifs.reversi.ok); - this.ai.api('games/reversi/match', { + this.ai.api('reversi/match', { userId: msg.userId }); } else { @@ -62,13 +67,13 @@ export default class extends Module { } } - @autobind + @bindThis private async onReversiInviteMe(inviter: any) { this.log(`Someone invited me: @${inviter.username}`); if (config.reversiEnabled) { // 承認 - const game = await this.ai.api('games/reversi/match', { + const game = await this.ai.api('reversi/match', { userId: inviter.id }); @@ -78,12 +83,12 @@ export default class extends Module { } } - @autobind + @bindThis private onReversiGameStart(game: any) { - this.log('enter reversi game room'); + this.log(`enter reversi game room: ${game.id}`); // ゲームストリームに接続 - const gw = this.ai.connection.connectToChannel('gamesReversiGame', { + const gw = this.ai.connection.connectToChannel('reversiGame', { gameId: game.id }); @@ -92,12 +97,12 @@ export default class extends Module { id: 'publish', type: 'switch', label: '藍が対局情報を投稿するのを許可', - value: true + value: true, }, { id: 'strength', type: 'radio', label: '強さ', - value: 3, + value: 4, items: [{ label: '接待', value: 0 @@ -117,7 +122,7 @@ export default class extends Module { }]; //#region バックエンドプロセス開始 - const ai = childProcess.fork(__dirname + '/back.js'); + const ai = childProcess.fork(_dirname + '/back.js'); // バックエンドプロセスに情報を渡す ai.send({ @@ -130,9 +135,10 @@ export default class extends Module { }); ai.on('message', (msg: Record) => { - if (msg.type == 'put') { - gw.send('set', { - pos: msg.pos + if (msg.type == 'putStone') { + gw.send('putStone', { + pos: msg.pos, + id: msg.id, }); } else if (msg.type == 'ended') { gw.dispose(); @@ -147,18 +153,13 @@ export default class extends Module { }); //#endregion - // フォーム初期化 - setTimeout(() => { - gw.send('initForm', form); - }, 1000); - // どんな設定内容の対局でも受け入れる setTimeout(() => { - gw.send('accept', {}); - }, 2000); + gw.send('ready', true); + }, 1000); } - @autobind + @bindThis private onGameEnded(game: any) { const user = game.user1Id == this.ai.account.id ? game.user2 : game.user1; diff --git a/src/modules/server/index.ts b/src/modules/server/index.ts index fc9d84c..17883df 100644 --- a/src/modules/server/index.ts +++ b/src/modules/server/index.ts @@ -1,7 +1,7 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; -import config from '@/config'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; +import config from '@/config.js'; export default class extends Module { public readonly name = 'server'; @@ -16,7 +16,7 @@ export default class extends Module { */ private statsLogs: any[] = []; - @autobind + @bindThis public install() { if (!config.serverMonitoring) return {}; @@ -35,7 +35,7 @@ export default class extends Module { return {}; } - @autobind + @bindThis private check() { const average = (arr) => arr.reduce((a, b) => a + b) / arr.length; @@ -48,12 +48,12 @@ export default class extends Module { } } - @autobind + @bindThis private async onStats(stats: any) { this.recentStat = stats; } - @autobind + @bindThis private warn() { //#region 前に警告したときから一旦落ち着いた状態を経験していなければ警告しない // 常に負荷が高いようなサーバーで無限に警告し続けるのを防ぐため diff --git a/src/modules/sleep-report/index.ts b/src/modules/sleep-report/index.ts index 50784f5..b75b70c 100644 --- a/src/modules/sleep-report/index.ts +++ b/src/modules/sleep-report/index.ts @@ -1,18 +1,18 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import serifs from '@/serifs.js'; export default class extends Module { public readonly name = 'sleepReport'; - @autobind + @bindThis public install() { this.report(); return {}; } - @autobind + @bindThis private report() { const now = Date.now(); diff --git a/src/modules/talk/index.ts b/src/modules/talk/index.ts index ca26965..7af6255 100644 --- a/src/modules/talk/index.ts +++ b/src/modules/talk/index.ts @@ -1,21 +1,21 @@ -import autobind from 'autobind-decorator'; -import { HandlerResult } from '@/ai'; -import Module from '@/module'; -import Message from '@/message'; -import serifs, { getSerif } from '@/serifs'; -import getDate from '@/utils/get-date'; +import { bindThis } from '@/decorators.js'; +import { HandlerResult } from '@/ai.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs, { getSerif } from '@/serifs.js'; +import getDate from '@/utils/get-date.js'; export default class extends Module { public readonly name = 'talk'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook, }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (!msg.text) return false; @@ -37,7 +37,7 @@ export default class extends Module { ); } - @autobind + @bindThis private greet(msg: Message): boolean { if (msg.text == null) return false; @@ -106,7 +106,7 @@ export default class extends Module { return false; } - @autobind + @bindThis private erait(msg: Message): boolean { const match = msg.extractedText.match(/(.+?)た(から|ので)(褒|ほ)めて/); if (match) { @@ -133,7 +133,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private omedeto(msg: Message): boolean { if (!msg.includes(['おめでと'])) return false; @@ -142,7 +142,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private nadenade(msg: Message): boolean { if (!msg.includes(['なでなで'])) return false; @@ -174,7 +174,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private kawaii(msg: Message): boolean { if (!msg.includes(['かわいい', '可愛い'])) return false; @@ -186,7 +186,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private suki(msg: Message): boolean { if (!msg.or(['好き', 'すき'])) return false; @@ -198,7 +198,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private hug(msg: Message): boolean { if (!msg.or(['ぎゅ', 'むぎゅ', /^はぐ(し(て|よ|よう)?)?$/])) return false; @@ -229,7 +229,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private humu(msg: Message): boolean { if (!msg.includes(['踏んで'])) return false; @@ -241,7 +241,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private batou(msg: Message): boolean { if (!msg.includes(['罵倒して', '罵って'])) return false; @@ -253,7 +253,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private itai(msg: Message): boolean { if (!msg.or(['痛い', 'いたい']) && !msg.extractedText.endsWith('痛い')) return false; @@ -262,7 +262,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private ote(msg: Message): boolean { if (!msg.or(['お手'])) return false; @@ -274,7 +274,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private ponkotu(msg: Message): boolean | HandlerResult { if (!msg.includes(['ぽんこつ'])) return false; @@ -285,7 +285,7 @@ export default class extends Module { }; } - @autobind + @bindThis private rmrf(msg: Message): boolean | HandlerResult { if (!msg.includes(['rm -rf'])) return false; @@ -296,7 +296,7 @@ export default class extends Module { }; } - @autobind + @bindThis private shutdown(msg: Message): boolean | HandlerResult { if (!msg.includes(['shutdown'])) return false; diff --git a/src/modules/timer/index.ts b/src/modules/timer/index.ts index b14a3a0..1f9d070 100644 --- a/src/modules/timer/index.ts +++ b/src/modules/timer/index.ts @@ -1,12 +1,12 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Message from '@/message'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Message from '@/message.js'; +import serifs from '@/serifs.js'; export default class extends Module { public readonly name = 'timer'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook, @@ -14,7 +14,7 @@ export default class extends Module { }; } - @autobind + @bindThis private async mentionHook(msg: Message) { const secondsQuery = (msg.text || '').match(/([0-9]+)秒/); const minutesQuery = (msg.text || '').match(/([0-9]+)分/); @@ -55,7 +55,7 @@ export default class extends Module { return true; } - @autobind + @bindThis private timeoutCallback(data) { const friend = this.ai.lookupFriend(data.userId); if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応 diff --git a/src/modules/valentine/index.ts b/src/modules/valentine/index.ts index 2a01690..a4d443f 100644 --- a/src/modules/valentine/index.ts +++ b/src/modules/valentine/index.ts @@ -1,12 +1,12 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; -import Friend from '@/friend'; -import serifs from '@/serifs'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; +import Friend from '@/friend.js'; +import serifs from '@/serifs.js'; export default class extends Module { public readonly name = 'valentine'; - @autobind + @bindThis public install() { this.crawleValentine(); setInterval(this.crawleValentine, 1000 * 60 * 3); @@ -17,7 +17,7 @@ export default class extends Module { /** * チョコ配り */ - @autobind + @bindThis private crawleValentine() { const now = new Date(); diff --git a/src/modules/welcome/index.ts b/src/modules/welcome/index.ts index 33bad7c..6237e69 100644 --- a/src/modules/welcome/index.ts +++ b/src/modules/welcome/index.ts @@ -1,10 +1,10 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; export default class extends Module { public readonly name = 'welcome'; - @autobind + @bindThis public install() { const tl = this.ai.connection.useSharedConnection('localTimeline'); @@ -13,7 +13,7 @@ export default class extends Module { return {}; } - @autobind + @bindThis private onLocalNote(note: any) { if (note.isFirstNote) { setTimeout(() => { diff --git a/src/stream.ts b/src/stream.ts index 696101c..6a82fdb 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -1,8 +1,10 @@ -import autobind from 'autobind-decorator'; +import { bindThis } from '@/decorators.js'; import { EventEmitter } from 'events'; -import * as WebSocket from 'ws'; -const ReconnectingWebsocket = require('reconnecting-websocket'); -import config from './config'; +import WebSocket from 'ws'; +import _ReconnectingWebsocket from 'reconnecting-websocket'; +import config from './config.js'; + +const ReconnectingWebsocket = _ReconnectingWebsocket as unknown as typeof _ReconnectingWebsocket['default']; /** * Misskey stream connection @@ -29,7 +31,7 @@ export default class Stream extends EventEmitter { this.stream.addEventListener('message', this.onMessage); } - @autobind + @bindThis public useSharedConnection(channel: string): SharedConnection { let pool = this.sharedConnectionPools.find(p => p.channel === channel); @@ -43,19 +45,19 @@ export default class Stream extends EventEmitter { return connection; } - @autobind + @bindThis public removeSharedConnection(connection: SharedConnection) { this.sharedConnections = this.sharedConnections.filter(c => c !== connection); } - @autobind + @bindThis public connectToChannel(channel: string, params?: any): NonSharedConnection { const connection = new NonSharedConnection(this, channel, params); this.nonSharedConnections.push(connection); return connection; } - @autobind + @bindThis public disconnectToChannel(connection: NonSharedConnection) { this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection); } @@ -63,7 +65,7 @@ export default class Stream extends EventEmitter { /** * Callback of when open connection */ - @autobind + @bindThis private onOpen() { const isReconnect = this.state == 'reconnecting'; @@ -91,7 +93,7 @@ export default class Stream extends EventEmitter { /** * Callback of when close connection */ - @autobind + @bindThis private onClose() { this.state = 'reconnecting'; this.emit('_disconnected_'); @@ -100,7 +102,7 @@ export default class Stream extends EventEmitter { /** * Callback of when received a message from connection */ - @autobind + @bindThis private onMessage(message) { const { type, body } = JSON.parse(message.data); @@ -128,7 +130,7 @@ export default class Stream extends EventEmitter { /** * Send a message to connection */ - @autobind + @bindThis public send(typeOrPayload, payload?) { const data = payload === undefined ? typeOrPayload : { type: typeOrPayload, @@ -147,7 +149,7 @@ export default class Stream extends EventEmitter { /** * Close this connection */ - @autobind + @bindThis public close() { this.stream.removeEventListener('open', this.onOpen); this.stream.removeEventListener('message', this.onMessage); @@ -169,7 +171,7 @@ class Pool { this.id = Math.random().toString(); } - @autobind + @bindThis public inc() { if (this.users === 0 && !this.isConnected) { this.connect(); @@ -184,7 +186,7 @@ class Pool { } } - @autobind + @bindThis public dec() { this.users--; @@ -198,7 +200,7 @@ class Pool { } } - @autobind + @bindThis public connect() { this.isConnected = true; this.stream.send('connect', { @@ -207,7 +209,7 @@ class Pool { }); } - @autobind + @bindThis private disconnect() { this.isConnected = false; this.disposeTimerId = null; @@ -227,7 +229,7 @@ abstract class Connection extends EventEmitter { this.channel = channel; } - @autobind + @bindThis public send(id: string, typeOrPayload, payload?) { const type = payload === undefined ? typeOrPayload.type : typeOrPayload; const body = payload === undefined ? typeOrPayload.body : payload; @@ -256,12 +258,12 @@ class SharedConnection extends Connection { this.pool.inc(); } - @autobind + @bindThis public send(typeOrPayload, payload?) { super.send(this.pool.id, typeOrPayload, payload); } - @autobind + @bindThis public dispose() { this.pool.dec(); this.removeAllListeners(); @@ -282,7 +284,7 @@ class NonSharedConnection extends Connection { this.connect(); } - @autobind + @bindThis public connect() { this.stream.send('connect', { channel: this.channel, @@ -291,12 +293,12 @@ class NonSharedConnection extends Connection { }); } - @autobind + @bindThis public send(typeOrPayload, payload?) { super.send(this.id, typeOrPayload, payload); } - @autobind + @bindThis public dispose() { this.removeAllListeners(); this.stream.send('disconnect', { id: this.id }); diff --git a/src/utils/includes.ts b/src/utils/includes.ts index 4213f5d..363221a 100644 --- a/src/utils/includes.ts +++ b/src/utils/includes.ts @@ -1,4 +1,4 @@ -import { katakanaToHiragana, hankakuToZenkaku } from './japanese'; +import { katakanaToHiragana, hankakuToZenkaku } from './japanese.js'; export default function(text: string, words: string[]): boolean { if (text == null) return false; diff --git a/src/utils/log.ts b/src/utils/log.ts index 0c1b9a9..8b9eba8 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -1,4 +1,4 @@ -import * as chalk from 'chalk'; +import chalk from 'chalk'; export default function(msg: string) { const now = new Date(); diff --git a/src/utils/or.ts b/src/utils/or.ts index 32a1b03..522d93d 100644 --- a/src/utils/or.ts +++ b/src/utils/or.ts @@ -1,4 +1,4 @@ -import { hankakuToZenkaku, katakanaToHiragana } from './japanese'; +import { hankakuToZenkaku, katakanaToHiragana } from './japanese.js'; export default function(text: string, words: (string | RegExp)[]): boolean { if (text == null) return false; diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..b813362 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,7 @@ +export function sleep(msec: number) { + return new Promise(res => { + setTimeout(() => { + res(); + }, msec); + }); +} diff --git a/src/vocabulary.ts b/src/vocabulary.ts index 1dc637c..9c90d45 100644 --- a/src/vocabulary.ts +++ b/src/vocabulary.ts @@ -1,4 +1,4 @@ -import * as seedrandom from 'seedrandom'; +import seedrandom from 'seedrandom'; export const itemPrefixes = [ 'プラチナ製', diff --git a/test/__modules__/test.ts b/test/__modules__/test.ts index 56ef18e..98be556 100644 --- a/test/__modules__/test.ts +++ b/test/__modules__/test.ts @@ -1,18 +1,18 @@ -import autobind from 'autobind-decorator'; -import Module from '@/module'; +import { bindThis } from '@/decorators.js'; +import Module from '@/module.js'; import Message from '@/message'; export default class extends Module { public readonly name = 'test'; - @autobind + @bindThis public install() { return { mentionHook: this.mentionHook }; } - @autobind + @bindThis private async mentionHook(msg: Message) { if (msg.text && msg.text.includes('ping')) { msg.reply('PONG!', { diff --git a/tsconfig.json b/tsconfig.json index f20004c..0231285 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,46 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "noEmitOnError": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noImplicitThis": true, - "noFallthroughCasesInSwitch": true, + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "rootDir": "./src", + "outDir": "./built/", + "removeComments": true, + "resolveJsonModule": true, + "strict": true, + "strictFunctionTypes": true, "strictNullChecks": true, "experimentalDecorators": true, - "sourceMap": false, - "target": "es2020", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "built", - "rootDir": "src", - "baseUrl": ".", + "noImplicitReturns": true, + "noImplicitAny": false, + "esModuleInterop": true, + "typeRoots": [ + "./node_modules/@types" + ], + "lib": [ + "esnext", + "dom" + ], "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] }, + "plugins": [ + // Transform paths in output .js files + { "transform": "typescript-transform-paths" }, + + // Transform paths in output .d.ts files (Include this line if you output declarations files) + { "transform": "typescript-transform-paths", "afterDeclarations": true } + ] }, - "compileOnSave": false, "include": [ - "./src/**/*.ts" - ] + "src/**/*" + ], + "exclude": [ + "node_modules", + "test/**/*" + ], }