数当てゲームを実装するなど

This commit is contained in:
syuilo 2018-08-12 23:03:00 +09:00
parent 1bf139b204
commit 24af1d80ae
14 changed files with 277 additions and 24 deletions

15
package-lock.json generated
View file

@ -8,6 +8,11 @@
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
}, },
"@types/lokijs": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.2.tgz",
"integrity": "sha512-ZF14v1P1Bjbw8VJRu+p4WS9V926CAOjWF4yq23QmSBWRPe0/GXlUKzSxjP1fi/xi8nrq6zr9ECo8Z/8KsRqroQ=="
},
"@types/node": { "@types/node": {
"version": "10.0.5", "version": "10.0.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.5.tgz",
@ -314,6 +319,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
}, },
"lokijs": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.5.tgz",
"integrity": "sha1-HCH4KvdXkDf63nueSBNIXCNwi7Y="
},
"make-error": { "make-error": {
"version": "1.3.4", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
@ -514,6 +524,11 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"timeout-as-promise": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/timeout-as-promise/-/timeout-as-promise-1.0.0.tgz",
"integrity": "sha1-c2foEfyZKs/Nzaq/LlDfr4shV28="
},
"tough-cookie": { "tough-cookie": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",

View file

@ -5,16 +5,19 @@
"build": "tsc" "build": "tsc"
}, },
"dependencies": { "dependencies": {
"@types/lokijs": "1.5.2",
"@types/node": "10.0.5", "@types/node": "10.0.5",
"@types/promise-retry": "1.1.2", "@types/promise-retry": "1.1.2",
"@types/seedrandom": "2.4.27", "@types/seedrandom": "2.4.27",
"@types/ws": "5.1.2", "@types/ws": "5.1.2",
"lokijs": "1.5.5",
"misskey-reversi": "0.0.5", "misskey-reversi": "0.0.5",
"promise-retry": "1.1.1", "promise-retry": "1.1.1",
"reconnecting-websocket": "4.0.0-rc5", "reconnecting-websocket": "4.0.0-rc5",
"request": "2.87.0", "request": "2.87.0",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.5",
"seedrandom": "2.4.3", "seedrandom": "2.4.3",
"timeout-as-promise": "1.0.0",
"ts-node": "6.0.3", "ts-node": "6.0.3",
"typescript": "2.8.3", "typescript": "2.8.3",
"ws": "6.0.0" "ws": "6.0.0"

View file

@ -6,13 +6,14 @@ import serifs from './serifs';
import config from './config'; import config from './config';
import IModule from './module'; import IModule from './module';
import MessageLike from './message-like'; import MessageLike from './message-like';
import { contexts } from './memory';
const ReconnectingWebSocket = require('../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js'); const ReconnectingWebSocket = require('../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js');
/** /**
* *
*/ */
export default class { export default class {
private account: any; public account: any;
/** /**
* *
@ -99,17 +100,31 @@ export default class 藍 {
} }
}, 1000); }, 1000);
this.modules.filter(m => m.hasOwnProperty('onMention')).some(m => { const context = !msg.isMessage && msg.replyId == null ? null : contexts.findOne(msg.isMessage ? {
return m.onMention(msg); isMessage: true,
userId: msg.userId
} : {
isMessage: false,
noteId: msg.replyId
}); });
if (context != null) {
const module = this.modules.find(m => m.name == context.module);
module.onReplyThisModule(msg);
} else {
this.modules.filter(m => m.hasOwnProperty('onMention')).some(m => {
return m.onMention(msg);
});
}
} }
public post = (param: any) => { public post = async (param: any) => {
this.api('notes/create', param); const res = await this.api('notes/create', param);
return res.createdNote;
} }
public sendMessage = (userId: any, param: any) => { public sendMessage = (userId: any, param: any) => {
this.api('messaging/messages/create', Object.assign({ return this.api('messaging/messages/create', Object.assign({
userId: userId, userId: userId,
}, param)); }, param));
} }
@ -121,4 +136,25 @@ export default class 藍 {
}, param) }, param)
}); });
}; };
public subscribeReply = (module: IModule, key: string, isMessage: boolean, id: string) => {
contexts.insertOne(isMessage ? {
isMessage: true,
userId: id,
module: module.name,
key: key,
} : {
isMessage: false,
noteId: id,
module: module.name,
key: key,
});
}
public unsubscribeReply = (module: IModule, key: string) => {
contexts.findAndRemove({
key: key,
module: module.name
});
}
} }

View file

@ -5,6 +5,7 @@ import ServerModule from './modules/server';
import PingModule from './modules/ping'; import PingModule from './modules/ping';
import EmojiModule from './modules/emoji'; import EmojiModule from './modules/emoji';
import FortuneModule from './modules/fortune'; import FortuneModule from './modules/fortune';
import GuessingGameModule from './modules/guessing-game';
import * as request from 'request-promise-native'; import * as request from 'request-promise-native';
const promiseRetry = require('promise-retry'); const promiseRetry = require('promise-retry');
@ -20,6 +21,7 @@ promiseRetry(retry => {
ai.install(new PingModule()); ai.install(new PingModule());
ai.install(new EmojiModule()); ai.install(new EmojiModule());
ai.install(new FortuneModule()); ai.install(new FortuneModule());
ai.install(new GuessingGameModule());
ai.install(new ServerModule()); ai.install(new ServerModule());
ai.install(new ReversiModule()); ai.install(new ReversiModule());
}); });

17
src/memory.ts Normal file
View file

@ -0,0 +1,17 @@
// 藍の記憶
import * as loki from 'lokijs';
const db = new loki('ai');
export default db;
export const contexts = db.addCollection<{
isMessage: boolean;
noteId?: string;
userId?: string;
module: string;
key: string;
}>('contexts', {
indices: ['key']
});

View file

@ -1,4 +1,5 @@
import from './ai'; import from './ai';
const delay = require('timeout-as-promise');
export default class MessageLike { export default class MessageLike {
private ai: ; private ai: ;
@ -21,27 +22,31 @@ export default class MessageLike {
return this.messageOrNote.text; return this.messageOrNote.text;
} }
public get replyId() {
return this.messageOrNote.replyId;
}
constructor(ai: , messageOrNote: any, isMessage: boolean) { constructor(ai: , messageOrNote: any, isMessage: boolean) {
this.ai = ai; this.ai = ai;
this.messageOrNote = messageOrNote; this.messageOrNote = messageOrNote;
this.isMessage = isMessage; this.isMessage = isMessage;
} }
public reply = (text: string, cw?: string) => { public reply = async (text: string, cw?: string) => {
console.log(`sending reply of ${this.id} ...`); console.log(`sending reply of ${this.id} ...`);
setTimeout(() => { await delay(2000);
if (this.isMessage) {
this.ai.sendMessage(this.messageOrNote.userId, { if (this.isMessage) {
text: text return await this.ai.sendMessage(this.messageOrNote.userId, {
}); text: text
} else { });
this.ai.post({ } else {
replyId: this.messageOrNote.id, return await this.ai.post({
text: text, replyId: this.messageOrNote.id,
cw: cw text: text,
}); cw: cw
} });
}, 2000); }
} }
} }

View file

@ -2,6 +2,8 @@ import 藍 from './ai';
import MessageLike from './message-like'; import MessageLike from './message-like';
export default interface IModule { export default interface IModule {
name: string;
install?: (ai: ) => void; install?: (ai: ) => void;
onMention?: (msg: MessageLike) => boolean; onMention?: (msg: MessageLike) => boolean;
onReplyThisModule?: (msg: MessageLike) => void;
} }

View file

@ -119,10 +119,12 @@ const faces = [
] ]
export default class EmojiModule implements IModule { export default class EmojiModule implements IModule {
public name = 'emoji';
public install = (ai: ) => { } public install = (ai: ) => { }
public onMention = (msg: MessageLike) => { public onMention = (msg: MessageLike) => {
if (msg.text && msg.text.includes('絵文字')) { if (msg.text && (msg.text.includes('絵文字') || msg.text.includes('emoji'))) {
const hand = hands[Math.floor(Math.random() * hands.length)]; const hand = hands[Math.floor(Math.random() * hands.length)];
const face = faces[Math.floor(Math.random() * faces.length)]; const face = faces[Math.floor(Math.random() * faces.length)];
const emoji = Array.isArray(hand) ? hand[0] + face + hand[1] : hand + face + hand; const emoji = Array.isArray(hand) ? hand[0] + face + hand[1] : hand + face + hand;

View file

@ -24,7 +24,9 @@ const items = [
'寿司' '寿司'
]; ];
export default class EmojiModule implements IModule { export default class FortuneModule implements IModule {
public name = 'fortune';
public install = (ai: ) => { } public install = (ai: ) => { }
public onMention = (msg: MessageLike) => { public onMention = (msg: MessageLike) => {

View file

@ -0,0 +1,118 @@
import from '../../ai';
import IModule from '../../module';
import MessageLike from '../../message-like';
import serifs from '../../serifs';
import db from '../../memory';
export const guesses = db.addCollection<{
userId: string;
secret: number;
tries: number[];
isEnded: boolean;
startedAt: number;
endedAt: number;
}>('guessingGame', {
indices: ['userId']
});
export default class GuessingGameModule implements IModule {
public name = 'guessingGame';
private ai: ;
public install = (ai: ) => {
this.ai = ai;
}
public onMention = (msg: MessageLike) => {
if (msg.text && msg.text.includes('数当て')) {
const exist = guesses.findOne({
userId: msg.userId,
isEnded: false
});
if (exist != null) {
msg.reply(serifs.GUESSINGGAME_ARLEADY_STARTED);
} else {
const secret = Math.floor(Math.random() * 100);
guesses.insertOne({
userId: msg.userId,
secret: secret,
tries: [],
isEnded: false,
startedAt: Date.now(),
endedAt: null
});
msg.reply(serifs.GUESSINGGAME_STARTED).then(reply => {
this.ai.subscribeReply(this, msg.userId, msg.isMessage, msg.isMessage ? msg.userId : reply.id);
});
}
return true;
} else {
return false;
}
}
public onReplyThisModule = (msg: MessageLike) => {
if (msg.text == null) return;
const exist = guesses.findOne({
userId: msg.userId,
isEnded: false
});
if (msg.text.includes('やめ')) {
msg.reply(serifs.GUESSINGGAME_CANCEL);
exist.isEnded = true;
exist.endedAt = Date.now();
guesses.update(exist);
this.ai.unsubscribeReply(this, msg.userId);
return;
}
const guess = msg.text.toLowerCase().replace(this.ai.account.username.toLowerCase(), '').match(/[0-9]+/);
if (guess == null) {
msg.reply(serifs.GUESSINGGAME_NAN).then(reply => {
this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id);
});
} else {
const g = parseInt(guess, 10);
const firsttime = exist.tries.indexOf(g) === -1;
let text: string;
let end = false;
if (exist.secret < g) {
text = firsttime
? serifs.GUESSINGGAME_LESS.replace('$', g.toString())
: serifs.GUESSINGGAME_LESS_AGAIN.replace('$', g.toString());
} else if (exist.secret > g) {
text = firsttime
? serifs.GUESSINGGAME_GRATER.replace('$', g.toString())
: serifs.GUESSINGGAME_GRATER_AGAIN.replace('$', g.toString());
} else {
end = true;
text = serifs.GUESSINGGAME_CONGRATS.replace('{tries}', exist.tries.length.toString());
}
if (end) {
exist.isEnded = true;
exist.endedAt = Date.now();
guesses.update(exist);
this.ai.unsubscribeReply(this, msg.userId);
} else {
exist.tries.push(g);
guesses.update(exist);
}
msg.reply(text).then(reply => {
if (!end) {
this.ai.subscribeReply(this, msg.userId, msg.isMessage, reply.id);
}
});
}
}
}

View file

@ -3,6 +3,8 @@ import IModule from '../../module';
import MessageLike from '../../message-like'; import MessageLike from '../../message-like';
export default class PingModule implements IModule { export default class PingModule implements IModule {
public name = 'ping';
public install = (ai: ) => { } public install = (ai: ) => { }
public onMention = (msg: MessageLike) => { public onMention = (msg: MessageLike) => {

View file

@ -8,6 +8,8 @@ import MessageLike from '../../message-like';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
export default class ReversiModule implements IModule { export default class ReversiModule implements IModule {
public name = 'reversi';
private ai: ; private ai: ;
/** /**
@ -38,7 +40,7 @@ export default class ReversiModule implements IModule {
} }
public onMention = (msg: MessageLike) => { public onMention = (msg: MessageLike) => {
if (msg.text && msg.text.includes('リバーシ')) { if (msg.text && (msg.text.includes('リバーシ') || msg.text.toLowerCase().includes('reversi'))) {
if (config.reversiEnabled) { if (config.reversiEnabled) {
msg.reply(serifs.REVERSI_OK); msg.reply(serifs.REVERSI_OK);

View file

@ -8,6 +8,8 @@ import MessageLike from '../../message-like';
const ReconnectingWebSocket = require('../../../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js'); const ReconnectingWebSocket = require('../../../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs.js');
export default class ServerModule implements IModule { export default class ServerModule implements IModule {
public name = 'server';
private ai: ; private ai: ;
private connection?: any; private connection?: any;
private preventScheduleReboot = false; private preventScheduleReboot = false;

View file

@ -35,5 +35,50 @@ export default {
*/ */
EMOJI_SUGGEST: 'こんなのはどうですか?→$', EMOJI_SUGGEST: 'こんなのはどうですか?→$',
FORTUNE_CW: '私が今日のあなたの運勢を占いました...' FORTUNE_CW: '私が今日のあなたの運勢を占いました...',
/**
*
*/
GUESSINGGAME_ARLEADY_STARTED: 'え、ゲームは既に始まってますよ!',
/**
*
*/
GUESSINGGAME_STARTED: '0~100の秘密の数を当ててみてください♪',
/**
*
*/
GUESSINGGAME_NAN: '数字でお願いします!「やめる」と言ってゲームをやめることもできますよ!',
/**
*
*/
GUESSINGGAME_CANCEL: 'わかりました~。ありがとうございました♪',
/**
*
*/
GUESSINGGAME_GRATER: '$より大きいですね',
/**
* (2)
*/
GUESSINGGAME_GRATER_AGAIN: 'もう一度言いますが$より大きいですよ!',
/**
*
*/
GUESSINGGAME_LESS: '$より小さいですね',
/**
* (2)
*/
GUESSINGGAME_LESS_AGAIN: 'もう一度言いますが$より小さいですよ!',
/**
*
*/
GUESSINGGAME_CONGRATS: '正解です🎉 ({tries}回目で当てました)',
}; };