diff --git a/src/config.ts b/src/config.ts index f729155..7e3f632 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,7 +5,6 @@ type Config = { apiUrl: string; keywordEnabled: boolean; reversiEnabled: boolean; - serverMonitoring: boolean; mecab?: string; }; diff --git a/src/friend.ts b/src/friend.ts index cc7f1a3..c1fc84c 100644 --- a/src/friend.ts +++ b/src/friend.ts @@ -1,9 +1,11 @@ import 藍 from './ai'; import IModule from './module'; +import getDate from './utils/get-date'; +import { User } from './misskey/user'; export type FriendDoc = { userId: string; - user: any; + user: User; name?: string; love?: number; lastLoveIncrementedAt?: string; @@ -50,7 +52,7 @@ export default class Friend { } } - public updateUser = (user: any) => { + public updateUser = (user: User) => { this.doc.user = user; this.save(); } @@ -79,11 +81,7 @@ export default class Friend { } public incLove = () => { - const now = new Date(); - const y = now.getFullYear(); - const m = now.getMonth(); - const d = now.getDate(); - const today = `${y}/${m + 1}/${d}`; + const today = getDate(); if (this.doc.lastLoveIncrementedAt != today) { this.doc.todayLoveIncrements = 0; diff --git a/src/index.ts b/src/index.ts index 1fe6f3e..215d001 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ import config from './config'; import CoreModule from './modules/core'; import ReversiModule from './modules/reversi'; -import ServerModule from './modules/server'; import PingModule from './modules/ping'; import EmojiModule from './modules/emoji'; import FortuneModule from './modules/fortune'; @@ -36,7 +35,6 @@ promiseRetry(retry => { ai.install(new GuessingGameModule()); ai.install(new ReversiModule()); ai.install(new TimerModule()); - if (config.serverMonitoring) ai.install(new ServerModule()); if (config.keywordEnabled) ai.install(new KeywordModule()); console.log('--- ai started! ---'); diff --git a/src/misskey/user.ts b/src/misskey/user.ts new file mode 100644 index 0000000..21c6be8 --- /dev/null +++ b/src/misskey/user.ts @@ -0,0 +1,5 @@ +export type User = { + id: string; + name: string; + username: string; +}; diff --git a/src/modules/core/index.ts b/src/modules/core/index.ts index 20900f7..6737a63 100644 --- a/src/modules/core/index.ts +++ b/src/modules/core/index.ts @@ -4,6 +4,7 @@ import IModule from '../../module'; import MessageLike from '../../message-like'; import serifs from '../../serifs'; import Friend from '../../friend'; +import getDate from '../../utils/get-date'; function zeroPadding(num: number, length: number): string { return ('0000000000' + num).slice(-length); @@ -76,12 +77,9 @@ export default class CoreModule implements IModule { return true; } - const withSan = - name.endsWith('さん') || - name.endsWith('くん') || - name.endsWith('君') || - name.endsWith('ちゃん') || - name.endsWith('様'); + const titles = ['さん', 'くん', '君', 'ちゃん', '様', '先生']; + + const withSan = titles.some(t => name.endsWith(t)); if (withSan) { msg.friend.updateName(name); @@ -101,11 +99,7 @@ export default class CoreModule implements IModule { if (!msg.text) return false; const incLove = () => { - const now = new Date(); - const y = now.getFullYear(); - const m = now.getMonth(); - const d = now.getDate(); - const today = `${y}/${m + 1}/${d}`; + const today = getDate(); const data = msg.friend.getPerModulesData(this); @@ -146,6 +140,7 @@ export default class CoreModule implements IModule { if (!msg.text) return false; if (!msg.text.includes('なでなで')) return false; + //#region 1日に1回だけ親愛度を上げる const now = new Date(); const y = now.getFullYear(); const m = now.getMonth(); @@ -160,6 +155,7 @@ export default class CoreModule implements IModule { msg.friend.incLove(); } + //#endregion msg.reply( msg.friend.love >= 5 ? serifs.core.nadenade2 : diff --git a/src/modules/emoji/index.ts b/src/modules/emoji/index.ts index 3666257..b4ff498 100644 --- a/src/modules/emoji/index.ts +++ b/src/modules/emoji/index.ts @@ -128,7 +128,7 @@ export default class EmojiModule implements IModule { const hand = hands[Math.floor(Math.random() * hands.length)]; const face = faces[Math.floor(Math.random() * faces.length)]; const emoji = Array.isArray(hand) ? hand[0] + face + hand[1] : hand + face + hand; - msg.reply(serifs.EMOJI_SUGGEST.replace('$', emoji)); + msg.reply(serifs.emoji.suggest.replace('$', emoji)); return true; } else { return false; diff --git a/src/modules/fortune/index.ts b/src/modules/fortune/index.ts index 35b2313..4b3faeb 100644 --- a/src/modules/fortune/index.ts +++ b/src/modules/fortune/index.ts @@ -36,7 +36,7 @@ export default class FortuneModule implements IModule { const rng = seedrandom(seed); const omikuji = omikujis[Math.floor(rng() * omikujis.length)]; const item = items[Math.floor(rng() * items.length)]; - msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, serifs.FORTUNE_CW); + msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, serifs.fortune.cw); return true; } else { return false; diff --git a/src/modules/guessing-game/index.ts b/src/modules/guessing-game/index.ts index 5ac982a..5911149 100644 --- a/src/modules/guessing-game/index.ts +++ b/src/modules/guessing-game/index.ts @@ -38,9 +38,9 @@ export default class GuessingGameModule implements IModule { if (!msg.isMessage) { if (exist != null) { - msg.reply(serifs.GUESSINGGAME_ARLEADY_STARTED); + msg.reply(serifs.guessingGame.arleadyStarted); } else { - msg.reply(serifs.GUESSINGGAME_PLZ_DM); + msg.reply(serifs.guessingGame.plzDm); } return true; @@ -57,7 +57,7 @@ export default class GuessingGameModule implements IModule { endedAt: null }); - msg.reply(serifs.GUESSINGGAME_STARTED).then(reply => { + msg.reply(serifs.guessingGame.started).then(reply => { this.ai.subscribeReply(this, msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id); }); @@ -76,7 +76,7 @@ export default class GuessingGameModule implements IModule { }); if (msg.text.includes('やめ')) { - msg.reply(serifs.GUESSINGGAME_CANCEL); + msg.reply(serifs.guessingGame.cancel); exist.isEnded = true; exist.endedAt = Date.now(); this.guesses.update(exist); @@ -87,7 +87,7 @@ export default class GuessingGameModule implements IModule { const guess = msg.text.toLowerCase().replace(this.ai.account.username.toLowerCase(), '').match(/[0-9]+/); if (guess == null) { - msg.reply(serifs.GUESSINGGAME_NAN).then(reply => { + msg.reply(serifs.guessingGame.nan).then(reply => { this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id); }); } else { @@ -104,15 +104,15 @@ export default class GuessingGameModule implements IModule { if (exist.secret < g) { text = firsttime - ? serifs.GUESSINGGAME_LESS.replace('$', g.toString()) - : serifs.GUESSINGGAME_LESS_AGAIN.replace('$', g.toString()); + ? serifs.guessingGame.less.replace('$', g.toString()) + : serifs.guessingGame.lessAgain.replace('$', g.toString()); } else if (exist.secret > g) { text = firsttime - ? serifs.GUESSINGGAME_GRATER.replace('$', g.toString()) - : serifs.GUESSINGGAME_GRATER_AGAIN.replace('$', g.toString()); + ? serifs.guessingGame.grater.replace('$', g.toString()) + : serifs.guessingGame.graterAgain.replace('$', g.toString()); } else { end = true; - text = serifs.GUESSINGGAME_CONGRATS.replace('{tries}', exist.tries.length.toString()); + text = serifs.guessingGame.congrats.replace('{tries}', exist.tries.length.toString()); } if (end) { diff --git a/src/modules/reversi/back.ts b/src/modules/reversi/back.ts index dab803f..51af0d7 100644 --- a/src/modules/reversi/back.ts +++ b/src/modules/reversi/back.ts @@ -9,6 +9,7 @@ import * as request from 'request-promise-native'; import Reversi, { Color } from 'misskey-reversi'; import config from '../../config'; +import serifs from '../../serifs'; const db = {}; @@ -208,36 +209,36 @@ class Session { private onEnded = async (msg: any) => { // ストリームから切断 process.send({ - type: 'close' + type: 'ended' }); let text: string; if (msg.body.game.surrendered) { if (this.isSettai) { - text = `(${this.userName}を接待していたら投了されちゃいました... ごめんなさい)`; + text = serifs.reversi.settaiButYouSurrendered.replace('{name}', this.userName); } else { - text = `${this.userName}が投了しちゃいました`; + text = serifs.reversi.youSurrendered.replace('{name}', this.userName); } } else if (msg.body.winnerId) { if (msg.body.winnerId == this.account.id) { if (this.isSettai) { - text = `${this.userName}に接待で勝ってしまいました...`; + text = serifs.reversi.iWonButSettai.replace('{name}', this.userName); } else { - text = `${this.userName}に勝ちました♪`; + text = serifs.reversi.iWon.replace('{name}', this.userName); } } else { if (this.isSettai) { - text = `(${this.userName}に接待で負けてあげました...♪)`; + text = serifs.reversi.iLoseButSettai.replace('{name}', this.userName); } else { - text = `${this.userName}に負けました...`; + text = serifs.reversi.iLose.replace('{name}', this.userName); } } } else { if (this.isSettai) { - text = `(${this.userName}に接待で引き分けました...)`; + text = serifs.reversi.drawnSettai.replace('{name}', this.userName); } else { - text = `${this.userName}と引き分けました~`; + text = serifs.reversi.drawn.replace('{name}', this.userName); } } @@ -416,8 +417,8 @@ class Session { */ private postGameStarted = async () => { const text = this.isSettai - ? `${this.userName}の接待を始めました!` - : `対局を${this.userName}と始めました! (強さ${this.strength})`; + ? serifs.reversi.startedSettai.replace('{name}', this.userName) + : serifs.reversi.started.replace('{name}', this.userName).replace('{strength}', this.strength.toString()); return await this.post(`${text}\n→[観戦する](${this.url})`); } diff --git a/src/modules/reversi/index.ts b/src/modules/reversi/index.ts index c5f9729..2317f8e 100644 --- a/src/modules/reversi/index.ts +++ b/src/modules/reversi/index.ts @@ -6,6 +6,9 @@ import serifs from '../../serifs'; import config from '../../config'; import MessageLike from '../../message-like'; import * as WebSocket from 'ws'; +import Friend from '../../friend'; +import getDate from '../../utils/get-date'; +import { User } from '../../misskey/user'; export default class ReversiModule implements IModule { public name = 'reversi'; @@ -44,13 +47,13 @@ export default class ReversiModule implements IModule { public onMention = (msg: MessageLike) => { if (msg.text && (msg.text.includes('リバーシ') || msg.text.includes('りばーし') || msg.text.includes('オセロ') || msg.text.includes('おせろ') || msg.text.toLowerCase().includes('reversi'))) { if (config.reversiEnabled) { - msg.reply(serifs.REVERSI_OK); + msg.reply(serifs.reversi.ok); this.ai.api('games/reversi/match', { userId: msg.userId }); } else { - msg.reply(serifs.REVERSI_DECLINE); + msg.reply(serifs.reversi.decline); } return true; @@ -157,8 +160,10 @@ export default class ReversiModule implements IModule { type: 'set', pos: msg.pos }); - } else if (msg.type == 'close') { + } else if (msg.type == 'ended') { gw.close(); + + this.onGameEnded(game); } }); @@ -189,4 +194,23 @@ export default class ReversiModule implements IModule { console.log('reversi game stream closed'); }); } + + private onGameEnded(game: any) { + const user = game.user1Id == this.ai.account.id ? game.user2 : game.user1; + + //#region 1日に1回だけ親愛度を上げる + const today = getDate(); + + const friend = new Friend(this.ai, { user: user }); + + const data = friend.getPerModulesData(this); + + if (data.lastPlayedAt != today) { + data.lastPlayedAt = today; + friend.setPerModulesData(this, data); + + friend.incLove(); + } + //#endregion + } } diff --git a/src/modules/server/index.ts b/src/modules/server/index.ts deleted file mode 100644 index 2f0018f..0000000 --- a/src/modules/server/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -import * as childProcess from 'child_process'; -import * as WebSocket from 'ws'; -import 藍 from '../../ai'; -import IModule from '../../module'; -import serifs from '../../serifs'; -import config from '../../config'; -import MessageLike from '../../message-like'; -const ReconnectingWebSocket = require('../../../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js'); - -export default class ServerModule implements IModule { - public name = 'server'; - - private ai: 藍; - private connection?: any; - private preventScheduleReboot = false; - private rebootTimer: NodeJS.Timer; - private rebootTimerSub: NodeJS.Timer; - private recentStat: any; - - /** - * 1秒後とのログ1分間分 - */ - private statsLogs: any[] = []; - - public install = (ai: 藍) => { - this.ai = ai; - - this.connection = new ReconnectingWebSocket(`${config.wsUrl}/server-stats`, [], { - WebSocket: WebSocket - }); - - this.connection.addEventListener('open', () => { - console.log('server-stats stream opened'); - }); - - this.connection.addEventListener('close', () => { - console.log('server-stats stream closed'); - }); - - this.connection.addEventListener('message', message => { - const msg = JSON.parse(message.data); - - this.onConnectionMessage(msg); - }); - - setInterval(() => { - this.statsLogs.unshift(this.recentStat); - if (this.statsLogs.length > 60) this.statsLogs.pop(); - }, 1000); - - setInterval(() => { - this.check(); - }, 3000); - } - - private check = () => { - const average = (arr) => arr.reduce((a, b) => a + b) / arr.length; - - const memPercentages = this.statsLogs.map(s => (s.mem.used / s.mem.total) * 100); - const memPercentage = average(memPercentages); - if (memPercentage >= 90) { - this.scheduleReboot('mem'); - } - - const cpuPercentages = this.statsLogs.map(s => s.cpu_usage * 100); - const cpuPercentage = average(cpuPercentages); - if (cpuPercentage >= 70) { - this.scheduleReboot('cpu'); - } - - console.log(`CPU: ${cpuPercentage}% | MEM: ${memPercentage}%`); - } - - private onConnectionMessage = (msg: any) => { - switch (msg.type) { - - case 'stats': { - this.onStats(msg.body); - break; - } - - default: - break; - } - } - - private onStats = async (stats: any) => { - this.recentStat = stats; - } - - private scheduleReboot = (reason: string) => { - if (this.preventScheduleReboot) return; - - this.preventScheduleReboot = true; - - this.ai.post({ - text: reason == 'cpu' ? serifs.REBOOT_SCHEDULED_CPU : serifs.REBOOT_SCHEDULED_MEM - }).then(post => { - this.ai.subscribeReply(this, 'reboot', false, post.id); - }); - - this.rebootTimer = setTimeout(() => { - childProcess.exec('forever restartall'); - }, 1000 * 60); - - this.rebootTimerSub = setTimeout(() => { - this.ai.post({ - cw: serifs.REBOOT, - text: serifs.REBOOT_DETAIL - }); - }, 1000 * 50); - } - - public onReplyThisModule = (msg: MessageLike) => { - if (msg.text == null) return; - - if (msg.text.includes('やめ') || msg.text.includes('まって')) { - if (msg.user.isAdmin) { - msg.reply(serifs.REBOOT_CANCEL_REQUESTED_ACCEPT); - - this.ai.post({ - text: serifs.REBOOT_CANCELED - }); - - this.cancelReboot(); - } else { - msg.reply(serifs.REBOOT_CANCEL_REQUESTED_REJECT); - } - } - - this.ai.unsubscribeReply(this, 'reboot'); - } - - public onMention = (msg: MessageLike) => { - if (msg.text && msg.text.includes('再起動しないで')) { - if (msg.user.isAdmin) { - msg.reply(serifs.REBOOT_CANCEL_REQUESTED_ACCEPT); - - this.ai.post({ - text: serifs.REBOOT_CANCELED - }); - - this.cancelReboot(); - } else { - msg.reply(serifs.REBOOT_CANCEL_REQUESTED_REJECT); - } - return true; - } else { - return false; - } - } - - private cancelReboot = () => { - clearTimeout(this.rebootTimer); - clearTimeout(this.rebootTimerSub); - - // 10分間延期 - setTimeout(() => { - this.preventScheduleReboot = false; - }, 1000 * 60 * 10); - } -} diff --git a/src/serifs.ts b/src/serifs.ts index 325e3f9..9905921 100644 --- a/src/serifs.ts +++ b/src/serifs.ts @@ -23,93 +23,142 @@ export default { }, /** - * リバーシへの誘いを承諾するとき + * リバーシ */ - REVERSI_OK: '良いですよ~', + reversi: { + /** + * リバーシへの誘いを承諾するとき + */ + ok: '良いですよ~', + + /** + * リバーシへの誘いを断るとき + */ + decline: 'ごめんなさい、今リバーシはするなと言われてます...', + + /** + * 対局開始 + */ + started: '対局を{name}と始めました! (強さ{strength})', + + /** + * 接待開始 + */ + startedSettai: '({name}の接待を始めました)', + + /** + * 勝ったとき + */ + iWon: '{name}に勝ちました♪', + + /** + * 接待のつもりが勝ってしまったとき + */ + iWonButSettai: '({name}に接待で勝ってしまいました...)', + + /** + * 負けたとき + */ + iLose: '{name}に負けました...', + + /** + * 接待で負けてあげたとき + */ + iLoseButSettai: '({name}に接待で負けてあげました...♪)', + + /** + * 引き分けたとき + */ + drawn: '{name}と引き分けました~', + + /** + * 接待で引き分けたとき + */ + drawnSettai: '({name}に接待で引き分けました...)', + + /** + * 相手が投了したとき + */ + youSurrendered: '{name}が投了しちゃいました', + + /** + * 接待してたら相手が投了したとき + */ + settaiButYouSurrendered: '({name}を接待していたら投了されちゃいました... ごめんなさい)', + }, /** - * リバーシへの誘いを断るとき + * 数当てゲーム */ - REVERSI_DECLINE: 'ごめんなさい、今リバーシはするなと言われてます...', + guessingGame: { + /** + * やろうと言われたけど既にやっているとき + */ + arleadyStarted: 'え、ゲームは既に始まってますよ!', - /** - * (メモリが足りないので)再起動がスケジュールされたとき - */ - REBOOT_SCHEDULED_MEM: 'サーバーの空きメモリが少なくなってきたので、1分後にサーバー再起動しますね!', + /** + * タイムライン上で誘われたとき + */ + plzDm: 'メッセージでやりましょう!', - /** - * (CPU使用率が高いので)再起動がスケジュールされたとき - */ - REBOOT_SCHEDULED_CPU: 'サーバーの負荷が高いので、1分後にサーバー再起動しますね!', + /** + * ゲーム開始 + */ + started: '0~100の秘密の数を当ててみてください♪', - /** - * まもなく再起動されるとき - */ - REBOOT: 'では、まもなくサーバーを再起動します!', - REBOOT_DETAIL: '(私も再起動に巻き込まれちゃうので、サーバーの再起動が完了したことのお知らせはできません...)', + /** + * 数字じゃない返信があったとき + */ + nan: '数字でお願いします!「やめる」と言ってゲームをやめることもできますよ!', - REBOOT_CANCEL_REQUESTED_ACCEPT: 'わかりました。再起動の予定を取り消しました!', - REBOOT_CANCEL_REQUESTED_REJECT: 'ごめんなさい、再起動の取り消しは管理者のみが行えます...', + /** + * 中止を要求されたとき + */ + cancel: 'わかりました~。ありがとうございました♪', - REBOOT_CANCELED: '再起動が取り消されました。お騒がせしました', + /** + * 小さい数を言われたとき + */ + grater: '$より大きいですね', + + /** + * 小さい数を言われたとき(2度目) + */ + graterAgain: 'もう一度言いますが$より大きいですよ!', + + /** + * 大きい数を言われたとき + */ + less: '$より小さいですね', + + /** + * 大きい数を言われたとき(2度目) + */ + lessAgain: 'もう一度言いますが$より小さいですよ!', + + /** + * 正解したとき + */ + congrats: '正解です🎉 ({tries}回目で当てました)', + }, /** * 絵文字生成 */ - EMOJI_SUGGEST: 'こんなのはどうですか?→$', - - FORTUNE_CW: '私が今日のあなたの運勢を占いました...', + emoji: { + suggest: 'こんなのはどうですか?→$', + }, /** - * 数当てゲームをやろうと言われたけど既にやっているとき + * 占い */ - GUESSINGGAME_ARLEADY_STARTED: 'え、ゲームは既に始まってますよ!', + fortune: { + cw: '私が今日のあなたの運勢を占いました...', + }, /** - * タイムライン上で数当てゲームに誘われたとき + * タイマー */ - GUESSINGGAME_PLZ_DM: 'メッセージでやりましょう!', - - /** - * 数当てゲーム開始 - */ - GUESSINGGAME_STARTED: '0~100の秘密の数を当ててみてください♪', - - /** - * 数当てゲームで数字じゃない返信があったとき - */ - GUESSINGGAME_NAN: '数字でお願いします!「やめる」と言ってゲームをやめることもできますよ!', - - /** - * 数当てゲーム中止を要求されたとき - */ - GUESSINGGAME_CANCEL: 'わかりました~。ありがとうございました♪', - - /** - * 数当てゲームで小さい数を言われたとき - */ - GUESSINGGAME_GRATER: '$より大きいですね', - - /** - * 数当てゲームで小さい数を言われたとき(2度目) - */ - GUESSINGGAME_GRATER_AGAIN: 'もう一度言いますが$より大きいですよ!', - - /** - * 数当てゲームで大きい数を言われたとき - */ - GUESSINGGAME_LESS: '$より小さいですね', - - /** - * 数当てゲームで大きい数を言われたとき(2度目) - */ - GUESSINGGAME_LESS_AGAIN: 'もう一度言いますが$より小さいですよ!', - - /** - * 数当てゲームで正解したとき - */ - GUESSINGGAME_CONGRATS: '正解です🎉 ({tries}回目で当てました)', - timer: { set: 'わかりました!', invalid: 'うーん...?', diff --git a/src/utils/get-date.ts b/src/utils/get-date.ts new file mode 100644 index 0000000..42594aa --- /dev/null +++ b/src/utils/get-date.ts @@ -0,0 +1,8 @@ +export default function (): string { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + const today = `${y}/${m + 1}/${d}`; + return today; +}