diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..edccf3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = tab +indent_size = 2 +charset = utf-8 +insert_final_newline = true + +[*.yml] +indent_style = space diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..0636b42 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,15 @@ +type Config = { + host: string; + i: string; + id: string; + wsUrl: string; + apiUrl: string; + reversiEnabled: boolean; +}; + +const config = require('../config.json'); + +config.wsUrl = config.host.replace('http', 'ws'); +config.apiUrl = config.host + '/api'; + +export default config as Config; diff --git a/src/index.ts b/src/index.ts index 899b09f..0034fe5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,70 +1,28 @@ -import * as childProcess from 'child_process'; import * as WebSocket from 'ws'; import * as request from 'request-promise-native'; const ReconnectingWebSocket = require('../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js'); import serifs from './serifs'; -const config = require('../config.json'); +import config from './config'; +import IModule from './module'; +import MessageLike from './message-like'; -const wsUrl = config.host.replace('http', 'ws'); -const apiUrl = config.host + '/api'; - -class MessageLike { - private ai: 藍; - private messageOrNote: any; - public isMessage: boolean; - - public get id() { - return this.messageOrNote.id; - } - - public get userId() { - return this.messageOrNote.userId; - } - - public get text() { - return this.messageOrNote.text; - } - - constructor(ai: 藍, messageOrNote: any, isMessage: boolean) { - this.ai = ai; - this.messageOrNote = messageOrNote; - this.isMessage = isMessage; - } - - public reply = (text: string) => { - setTimeout(() => { - if (this.isMessage) { - this.ai.sendMessage(this.messageOrNote.userId, { - text: text - }); - } else { - this.ai.post({ - replyId: this.messageOrNote.id, - text: text - }); - } - }, 2000); - } -} +import ReversiModule from './modules/reversi'; /** * 藍 */ -class 藍 { +export default class 藍 { /** * ホームストリーム */ private connection: any; - /** - * リバーシストリーム - */ - private reversiConnection?: any; + private modules: IModule[] = []; constructor() { - this.connection = new ReconnectingWebSocket(`${wsUrl}/?i=${config.i}`, [], { + this.connection = new ReconnectingWebSocket(`${config.wsUrl}/?i=${config.i}`, [], { WebSocket: WebSocket }); @@ -83,26 +41,13 @@ class 藍 { }); if (config.reversiEnabled) { - this.reversiConnection = new ReconnectingWebSocket(`${wsUrl}/games/reversi?i=${config.i}`, [], { - WebSocket: WebSocket - }); - - this.reversiConnection.addEventListener('open', () => { - console.log('reversi stream opened'); - }); - - this.reversiConnection.addEventListener('close', () => { - console.log('reversi stream closed'); - }); - - this.reversiConnection.addEventListener('message', message => { - const msg = JSON.parse(message.data); - - this.onReversiConnectionMessage(msg); - }); } } + public install = (module: IModule) => { + this.modules.push(module); + } + private onMessage = (msg: any) => { switch (msg.type) { // メンションされたとき @@ -131,189 +76,51 @@ class 藍 { } } - private onReversiConnectionMessage = (msg: any) => { - switch (msg.type) { - - // 招待されたとき - case 'invited': { - this.onReversiInviteMe(msg.body.parent); - break; - } - - // マッチしたとき - case 'matched': { - this.onReversiGameStart(msg.body); - break; - } - - default: - break; - } - } - - private onReversiInviteMe = async (inviter: any) => { - console.log(`Someone invited me: @${inviter.username}`); - - if (config.reversiEnabled) { - // 承認 - const game = await request.post(`${apiUrl}/games/reversi/match`, { - json: { - i: config.i, - userId: inviter.id - } - }); - - this.onReversiGameStart(game); - } else { - // todo (リバーシできない旨をメッセージで伝えるなど) - } - } - - private onReversiGameStart = (game: any) => { - // ゲームストリームに接続 - const gw = new ReconnectingWebSocket(`${wsUrl}/games/reversi-game?i=${config.i}&game=${game.id}`, [], { - WebSocket: WebSocket - }); - - function send(msg) { - try { - gw.send(JSON.stringify(msg)); - } catch (e) { - console.error(e); - } - } - - gw.addEventListener('open', () => { - console.log('reversi game stream opened'); - - // フォーム - const form = [{ - id: 'publish', - type: 'switch', - label: '藍が対局情報を投稿するのを許可', - value: true - }, { - id: 'strength', - type: 'radio', - label: '強さ', - value: 3, - items: [{ - label: '接待', - value: 0 - }, { - label: '弱', - value: 2 - }, { - label: '中', - value: 3 - }, { - label: '強', - value: 4 - }, { - label: '最強', - value: 5 - }] - }]; - - //#region バックエンドプロセス開始 - const ai = childProcess.fork(__dirname + '/back.js'); - - // バックエンドプロセスに情報を渡す - ai.send({ - type: '_init_', - game, - form - }); - - ai.on('message', msg => { - if (msg.type == 'put') { - send({ - type: 'set', - pos: msg.pos - }); - } else if (msg.type == 'close') { - gw.close(); - } - }); - - // ゲームストリームから情報が流れてきたらそのままバックエンドプロセスに伝える - gw.addEventListener('message', message => { - const msg = JSON.parse(message.data); - ai.send(msg); - }); - //#endregion - - // フォーム初期化 - setTimeout(() => { - send({ - type: 'init-form', - body: form - }); - }, 1000); - - // どんな設定内容の対局でも受け入れる - setTimeout(() => { - send({ - type: 'accept' - }); - }, 2000); - }); - - gw.addEventListener('close', () => { - console.log('reversi game stream closed'); - }); - } - - private onMention = (x: MessageLike) => { + private onMention = (msg: MessageLike) => { // リアクションする - if (!x.isMessage) { + if (!msg.isMessage) { setTimeout(() => { - request.post(`${apiUrl}/notes/reactions/create`, { + request.post(`${config.apiUrl}/notes/reactions/create`, { json: { i: config.i, - noteId: x.id, + noteId: msg.id, reaction: 'love' } }); }, 1000); } - if (x.text && x.text.indexOf('リバーシ') > -1) { - if (config.reversiEnabled) { - x.reply(serifs.REVERSI_OK); - - request.post(`${apiUrl}/games/reversi/match`, { - json: { - i: config.i, - userId: x.userId - } - }); - } else { - x.reply(serifs.REVERSI_DECLINE); - } - } + this.modules.filter(m => m.hasOwnProperty('onMention')).some(m => { + return m.onMention(msg); + }); } public post = (param: any) => { setTimeout(() => { - request.post(`${apiUrl}/notes/create`, { - json: Object.assign({ - i: config.i - }, param) - }); + request.post('notes/create', param); }, 2000); } public sendMessage = (userId: any, param: any) => { setTimeout(() => { - request.post(`${apiUrl}/messaging/messages/create`, { - json: Object.assign({ - i: config.i, - userId: userId, - }, param) - }); + this.api('messages/create', Object.assign({ + userId: userId, + }, param)); }, 2000); } + + public api = (endpoint: string, param) => { + return request.post(`${config.apiUrl}/${endpoint}`, { + json: Object.assign({ + i: config.i + }, param) + }); + }; } const ai = new 藍(); + +if (config.reversiEnabled) { + const reversiModule = new ReversiModule(); + ai.install(reversiModule); +} diff --git a/src/message-like.ts b/src/message-like.ts new file mode 100644 index 0000000..951694e --- /dev/null +++ b/src/message-like.ts @@ -0,0 +1,40 @@ +import 藍 from '.'; + +export default class MessageLike { + private ai: 藍; + private messageOrNote: any; + public isMessage: boolean; + + public get id() { + return this.messageOrNote.id; + } + + public get userId() { + return this.messageOrNote.userId; + } + + public get text() { + return this.messageOrNote.text; + } + + constructor(ai: 藍, messageOrNote: any, isMessage: boolean) { + this.ai = ai; + this.messageOrNote = messageOrNote; + this.isMessage = isMessage; + } + + public reply = (text: string) => { + setTimeout(() => { + if (this.isMessage) { + this.ai.sendMessage(this.messageOrNote.userId, { + text: text + }); + } else { + this.ai.post({ + replyId: this.messageOrNote.id, + text: text + }); + } + }, 2000); + } +} diff --git a/src/module.ts b/src/module.ts new file mode 100644 index 0000000..a2285f3 --- /dev/null +++ b/src/module.ts @@ -0,0 +1,7 @@ +import 藍 from '.'; +import MessageLike from './message-like'; + +export default interface IModule { + install?: (ai: 藍) => void; + onMention?: (msg: MessageLike) => boolean; +} diff --git a/src/back.ts b/src/modules/reversi/back.ts similarity index 100% rename from src/back.ts rename to src/modules/reversi/back.ts diff --git a/src/modules/reversi/index.ts b/src/modules/reversi/index.ts new file mode 100644 index 0000000..aadf38a --- /dev/null +++ b/src/modules/reversi/index.ts @@ -0,0 +1,189 @@ +import * as childProcess from 'child_process'; + +const ReconnectingWebSocket = require('../../../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js'); + +import 藍 from '../..'; +import IModule from '../../module'; +import serifs from '../../serifs'; +import config from '../../config'; +import MessageLike from '../../message-like'; + +export default class ReversiModule implements IModule { + private ai: 藍; + + /** + * リバーシストリーム + */ + private reversiConnection?: any; + + public install = (ai: 藍) => { + this.ai = ai; + + this.reversiConnection = new ReconnectingWebSocket(`${config.wsUrl}/games/reversi?i=${config.i}`, [], { + WebSocket: WebSocket + }); + + this.reversiConnection.addEventListener('open', () => { + console.log('reversi stream opened'); + }); + + this.reversiConnection.addEventListener('close', () => { + console.log('reversi stream closed'); + }); + + this.reversiConnection.addEventListener('message', message => { + const msg = JSON.parse(message.data); + + this.onReversiConnectionMessage(msg); + }); + } + + public onMention = (msg: MessageLike) => { + if (msg.text && msg.text.indexOf('リバーシ') > -1) { + if (config.reversiEnabled) { + msg.reply(serifs.REVERSI_OK); + + this.ai.api('games/reversi/match', { + userId: msg.userId + }); + } else { + msg.reply(serifs.REVERSI_DECLINE); + } + + return true; + } else { + return false; + } + } + + + private onReversiConnectionMessage = (msg: any) => { + switch (msg.type) { + + // 招待されたとき + case 'invited': { + this.onReversiInviteMe(msg.body.parent); + break; + } + + // マッチしたとき + case 'matched': { + this.onReversiGameStart(msg.body); + break; + } + + default: + break; + } + } + + private onReversiInviteMe = async (inviter: any) => { + console.log(`Someone invited me: @${inviter.username}`); + + if (config.reversiEnabled) { + // 承認 + const game = await this.ai.api('games/reversi/match', { + userId: inviter.id + }); + + this.onReversiGameStart(game); + } else { + // todo (リバーシできない旨をメッセージで伝えるなど) + } + } + + private onReversiGameStart = (game: any) => { + // ゲームストリームに接続 + const gw = new ReconnectingWebSocket(`${config.wsUrl}/games/reversi-game?i=${config.i}&game=${game.id}`, [], { + WebSocket: WebSocket + }); + + function send(msg) { + try { + gw.send(JSON.stringify(msg)); + } catch (e) { + console.error(e); + } + } + + gw.addEventListener('open', () => { + console.log('reversi game stream opened'); + + // フォーム + const form = [{ + id: 'publish', + type: 'switch', + label: '藍が対局情報を投稿するのを許可', + value: true + }, { + id: 'strength', + type: 'radio', + label: '強さ', + value: 3, + items: [{ + label: '接待', + value: 0 + }, { + label: '弱', + value: 2 + }, { + label: '中', + value: 3 + }, { + label: '強', + value: 4 + }, { + label: '最強', + value: 5 + }] + }]; + + //#region バックエンドプロセス開始 + const ai = childProcess.fork(__dirname + '/back.js'); + + // バックエンドプロセスに情報を渡す + ai.send({ + type: '_init_', + game, + form + }); + + ai.on('message', msg => { + if (msg.type == 'put') { + send({ + type: 'set', + pos: msg.pos + }); + } else if (msg.type == 'close') { + gw.close(); + } + }); + + // ゲームストリームから情報が流れてきたらそのままバックエンドプロセスに伝える + gw.addEventListener('message', message => { + const msg = JSON.parse(message.data); + ai.send(msg); + }); + //#endregion + + // フォーム初期化 + setTimeout(() => { + send({ + type: 'init-form', + body: form + }); + }, 1000); + + // どんな設定内容の対局でも受け入れる + setTimeout(() => { + send({ + type: 'accept' + }); + }, 2000); + }); + + gw.addEventListener('close', () => { + console.log('reversi game stream closed'); + }); + } +} diff --git a/src/serifs.ts b/src/serifs.ts index 26bb0df..fcb861d 100644 --- a/src/serifs.ts +++ b/src/serifs.ts @@ -1,4 +1,11 @@ export default { + /** + * リバーシへの誘いを承諾するとき + */ REVERSI_OK: '良いですよ~', + + /** + * リバーシへの誘いを断るとき + */ REVERSI_DECLINE: 'ごめんなさい、今リバーシはするなと言われてます...' };