nanka iroiro

This commit is contained in:
syuilo 2020-09-02 21:54:01 +09:00
parent bc1c115de4
commit ec681a6705
21 changed files with 104 additions and 78 deletions

View file

@ -1,5 +1,5 @@
{ {
"name": "ai", "_v": "1.1",
"main": "./built/index.js", "main": "./built/index.js",
"scripts": { "scripts": {
"start": "node ./built", "start": "node ./built",

View file

@ -15,13 +15,14 @@ import Friend, { FriendDoc } from './friend';
import { User } from './misskey/user'; import { User } from './misskey/user';
import Stream from './stream'; import Stream from './stream';
import log from './utils/log'; import log from './utils/log';
const pkg = require('../package.json');
type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>; type MentionHook = (msg: Message) => Promise<boolean | HandlerResult>;
type ContextHook = (msg: Message, data?: any) => Promise<void | HandlerResult>; type ContextHook = (msg: Message, data?: any) => Promise<void | HandlerResult>;
type TimeoutCallback = (data?: any) => void; type TimeoutCallback = (data?: any) => void;
export type HandlerResult = { export type HandlerResult = {
reaction: string; reaction: string | null;
}; };
export type InstallerResult = { export type InstallerResult = {
@ -38,6 +39,7 @@ export type Meta = {
* *
*/ */
export default class { export default class {
public readonly version = pkg._v;
public account: User; public account: User;
public connection: Stream; public connection: Stream;
public modules: Module[] = []; public modules: Module[] = [];
@ -54,7 +56,7 @@ export default class 藍 {
noteId?: string; noteId?: string;
userId?: string; userId?: string;
module: string; module: string;
key: string; key: string | null;
data?: any; data?: any;
}>; }>;
@ -215,7 +217,7 @@ export default class 藍 {
noteId: msg.replyId noteId: msg.replyId
}); });
let reaction = 'love'; let reaction: string | null = 'love';
//#region //#region
// コンテキストがあればコンテキストフック呼び出し // コンテキストがあればコンテキストフック呼び出し
@ -228,7 +230,7 @@ export default class 藍 {
reaction = res.reaction; reaction = res.reaction;
} }
} else { } else {
let res: boolean | HandlerResult; let res: boolean | HandlerResult | null = null;
for (const handler of this.mentionHooks) { for (const handler of this.mentionHooks) {
res = await handler(msg); res = await handler(msg);
@ -367,7 +369,7 @@ export default class 藍 {
* @param data * @param data
*/ */
@autobind @autobind
public subscribeReply(module: Module, key: string, isDm: boolean, id: string, data?: any) { public subscribeReply(module: Module, key: string | null, isDm: boolean, id: string, data?: any) {
this.contexts.insertOne(isDm ? { this.contexts.insertOne(isDm ? {
isDm: true, isDm: true,
userId: id, userId: id,
@ -389,7 +391,7 @@ export default class 藍 {
* @param key * @param key
*/ */
@autobind @autobind
public unsubscribeReply(module: Module, key: string) { public unsubscribeReply(module: Module, key: string | null) {
this.contexts.findAndRemove({ this.contexts.findAndRemove({
key: key, key: key,
module: module.name module: module.name

View file

@ -8,7 +8,7 @@ import { genItem } from './vocabulary';
export type FriendDoc = { export type FriendDoc = {
userId: string; userId: string;
user: User; user: User;
name?: string; name?: string | null;
love?: number; love?: number;
lastLoveIncrementedAt?: string; lastLoveIncrementedAt?: string;
todayLoveIncrements?: number; todayLoveIncrements?: number;
@ -42,21 +42,30 @@ export default class Friend {
this.ai = ai; this.ai = ai;
if (opts.user) { if (opts.user) {
this.doc = this.ai.friends.findOne({ const exist = this.ai.friends.findOne({
userId: opts.user.id userId: opts.user.id
}); });
if (this.doc == null) { if (exist == null) {
this.doc = this.ai.friends.insertOne({ const inserted = this.ai.friends.insertOne({
userId: opts.user.id, userId: opts.user.id,
user: opts.user user: opts.user
}); });
if (inserted == null) {
throw new Error('Failed to insert friend doc');
}
this.doc = inserted;
} else { } else {
this.doc = exist;
this.doc.user = opts.user; this.doc.user = opts.user;
this.save(); this.save();
} }
} else { } else if (opts.doc) {
this.doc = opts.doc; this.doc = opts.doc;
} else {
throw new Error('No friend info specified');
} }
} }
@ -100,7 +109,7 @@ export default class Friend {
} }
// 1日に上げられる親愛度は最大3 // 1日に上げられる親愛度は最大3
if (this.doc.lastLoveIncrementedAt == today && this.doc.todayLoveIncrements >= 3) return; if (this.doc.lastLoveIncrementedAt == today && (this.doc.todayLoveIncrements || 0) >= 3) return;
if (this.doc.love == null) this.doc.love = 0; if (this.doc.love == null) this.doc.love = 0;
this.doc.love++; this.doc.love++;

View file

@ -7,6 +7,7 @@ const promiseRetry = require('promise-retry');
import from './ai'; import from './ai';
import config from './config'; import config from './config';
import _log from './utils/log'; import _log from './utils/log';
const pkg = require('../package.json');
import CoreModule from './modules/core'; import CoreModule from './modules/core';
import TalkModule from './modules/talk'; import TalkModule from './modules/talk';
@ -39,7 +40,7 @@ function log(msg: string): void {
_log(`[Boot]: ${msg}`); _log(`[Boot]: ${msg}`);
} }
log(chalk.bold('Ai v1.0')); log(chalk.bold(`Ai v${pkg._v}`));
promiseRetry(retry => { promiseRetry(retry => {
log(`Account fetching... ${chalk.gray(config.host)}`); log(`Account fetching... ${chalk.gray(config.host)}`);

View file

@ -60,47 +60,32 @@ export default class Message {
} }
@autobind @autobind
public async reply(text: string, cw?: string, renote?: string) { public async reply(text: string | null, opts?: {
file?: any;
cw?: string;
renote?: string;
immediate?: boolean;
}) {
if (text == null) return; if (text == null) return;
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`); this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
if (!opts?.immediate) {
await delay(2000); await delay(2000);
}
if (this.isDm) { if (this.isDm) {
return await this.ai.sendMessage(this.messageOrNote.userId, { return await this.ai.sendMessage(this.messageOrNote.userId, {
text: text text: text,
fileId: opts?.file?.id
}); });
} else { } else {
return await this.ai.post({ return await this.ai.post({
replyId: this.messageOrNote.id, replyId: this.messageOrNote.id,
text: text, text: text,
cw: cw, fileIds: opts?.file ? [opts?.file.id] : undefined,
renoteId: renote cw: opts?.cw,
}); renoteId: opts?.renote
}
}
@autobind
public async replyWithFile(text: string, file: any, cw?: string, renote?: string) {
if (text == null) return;
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
await delay(2000);
if (this.isDm) {
return await this.ai.sendMessage(this.messageOrNote.userId, {
text: text,
fileId: file.id
});
} else {
return await this.ai.post({
replyId: this.messageOrNote.id,
text: text,
fileIds: [file.id],
cw: cw,
renoteId: renote
}); });
} }
} }

View file

@ -37,7 +37,7 @@ export default abstract class Module {
* @param data * @param data
*/ */
@autobind @autobind
protected subscribeReply(key: string, isDm: boolean, id: string, data?: any) { protected subscribeReply(key: string | null, isDm: boolean, id: string, data?: any) {
this.ai.subscribeReply(this, key, isDm, id, data); this.ai.subscribeReply(this, key, isDm, id, data);
} }
@ -46,7 +46,7 @@ export default abstract class Module {
* @param key * @param key
*/ */
@autobind @autobind
protected unsubscribeReply(key: string) { protected unsubscribeReply(key: string | null) {
this.ai.unsubscribeReply(this, key); this.ai.unsubscribeReply(this, key);
} }

View file

@ -101,7 +101,7 @@ export default class extends Module {
const diffRange = 150; const diffRange = 150;
const datasetCount = 1 + Math.floor(Math.random() * 3); const datasetCount = 1 + Math.floor(Math.random() * 3);
let datasets = []; let datasets: any[] = [];
for (let d = 0; d < datasetCount; d++) { for (let d = 0; d < datasetCount; d++) {
let values = [Math.random() * 1000]; let values = [Math.random() * 1000];
@ -151,7 +151,7 @@ export default class extends Module {
}); });
this.log('Replying...'); this.log('Replying...');
msg.replyWithFile(serifs.chart.foryou, file); msg.reply(serifs.chart.foryou, { file });
return { return {
reaction: 'like' reaction: 'like'

View file

@ -96,7 +96,7 @@ export function renderChart(chart: Chart) {
ctx.fillText(step.toString(), chartAreaX, chartAreaY + y - 8); ctx.fillText(step.toString(), chartAreaX, chartAreaY + y - 8);
} }
const newDatasets = []; const newDatasets: any[] = [];
for (let series = 0; series < serieses; series++) { for (let series = 0; series < serieses; series++) {
newDatasets.push({ newDatasets.push({
@ -127,7 +127,7 @@ export function renderChart(chart: Chart) {
ctx.lineCap = 'round'; ctx.lineCap = 'round';
for (let xAxis = 0; xAxis < xAxisCount; xAxis++) { for (let xAxis = 0; xAxis < xAxisCount; xAxis++) {
const xAxisPerTypeHeights = []; const xAxisPerTypeHeights: number[] = [];
for (let series = 0; series < serieses; series++) { for (let series = 0; series < serieses; series++) {
const v = newDatasets[series].data[xAxis]; const v = newDatasets[series].data[xAxis];
@ -175,7 +175,7 @@ function niceScale(lowerBound: number, upperBound: number, ticks: number): numbe
// //
// Output will be an array of the Y axis values that // Output will be an array of the Y axis values that
// encompass the Y values. // encompass the Y values.
const steps = []; const steps: number[] = [];
// Determine Range // Determine Range
const range = upperBound - lowerBound; const range = upperBound - lowerBound;

View file

@ -25,7 +25,8 @@ export default class extends Module {
this.transferBegin(msg) || this.transferBegin(msg) ||
this.transferEnd(msg) || this.transferEnd(msg) ||
this.setName(msg) || this.setName(msg) ||
this.modules(msg) this.modules(msg) ||
this.version(msg)
); );
} }
@ -79,7 +80,7 @@ export default class extends Module {
return true; return true;
} }
const name = msg.text.match(/^(.+?)って呼んで/)[1]; const name = msg.text.match(/^(.+?)って呼んで/)![1];
if (name.length > 10) { if (name.length > 10) {
msg.reply(serifs.core.tooLong); msg.reply(serifs.core.tooLong);
@ -120,7 +121,21 @@ export default class extends Module {
text += '```'; text += '```';
msg.reply(text); msg.reply(text, {
immediate: true
});
return true;
}
@autobind
private version(msg: Message): boolean {
if (!msg.text) return false;
if (!msg.or(['v', 'version', 'バージョン'])) return false;
msg.reply(`\`\`\`\nv${this.ai.version}\n\`\`\``, {
immediate: true
});
return true; return true;
} }

View file

@ -37,7 +37,9 @@ export default class extends Module {
const rng = seedrandom(seed); const rng = seedrandom(seed);
const omikuji = blessing[Math.floor(rng() * blessing.length)]; const omikuji = blessing[Math.floor(rng() * blessing.length)];
const item = genItem(rng); const item = genItem(rng);
msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, serifs.fortune.cw(msg.friend.name)); msg.reply(`**${omikuji}🎉**\nラッキーアイテム: ${item}`, {
cw: serifs.fortune.cw(msg.friend.name)
});
return true; return true;
} else { } else {
return false; return false;

View file

@ -13,7 +13,7 @@ export default class extends Module {
tries: number[]; tries: number[];
isEnded: boolean; isEnded: boolean;
startedAt: number; startedAt: number;
endedAt: number; endedAt: number | null;
}>; }>;
@autobind @autobind
@ -74,6 +74,12 @@ export default class extends Module {
isEnded: false isEnded: false
}); });
// 処理の流れ上、実際にnullになることは無さそうだけど一応
if (exist == null) {
this.unsubscribeReply(msg.userId);
return;
}
if (msg.text.includes('やめ')) { if (msg.text.includes('やめ')) {
msg.reply(serifs.guessingGame.cancel); msg.reply(serifs.guessingGame.cancel);
exist.isEnded = true; exist.isEnded = true;

View file

@ -51,7 +51,9 @@ export default class extends Module {
if (recentGame) { if (recentGame) {
// 現在アクティブなゲームがある場合 // 現在アクティブなゲームがある場合
if (!recentGame.isEnded) { if (!recentGame.isEnded) {
msg.reply(serifs.kazutori.alreadyStarted, null, recentGame.postId); msg.reply(serifs.kazutori.alreadyStarted, {
renote: recentGame.postId
});
return true; return true;
} }
@ -90,6 +92,9 @@ export default class extends Module {
isEnded: false isEnded: false
}); });
// 処理の流れ上、実際にnullになることは無さそうだけど一応
if (game == null) return;
// 既に数字を取っていたら // 既に数字を取っていたら
if (game.votes.some(x => x.user.id == msg.userId)) return { if (game.votes.some(x => x.user.id == msg.userId)) return {
reaction: 'confused' reaction: 'confused'
@ -175,7 +180,7 @@ export default class extends Module {
} }
let results: string[] = []; let results: string[] = [];
let winner: User = null; let winner: User | null = null;
for (let i = 100; i >= 0; i--) { for (let i = 100; i >= 0; i--) {
const users = game.votes const users = game.votes

View file

@ -149,7 +149,7 @@ export function genMaze(seed, complexity?) {
let dir: Dir; let dir: Dir;
if (straightMode && rand(straightness) !== 0) { if (straightMode && rand(straightness) !== 0) {
if (dirs.includes(prevDir)) { if (prevDir != null && dirs.includes(prevDir)) {
dir = prevDir; dir = prevDir;
} else { } else {
dir = dirs[rand(dirs.length)]; dir = dirs[rand(dirs.length)];
@ -158,7 +158,7 @@ export function genMaze(seed, complexity?) {
dir = dirs[rand(dirs.length)]; dir = dirs[rand(dirs.length)];
} }
maze[x][y] = cellVariants[maze[x][y]].digg[dir]; maze[x][y] = cellVariants[maze[x][y]].digg[dir]!;
if (dir === 'top') { if (dir === 'top') {
maze[x][y - 1] = maze[x][y - 1] === 'empty' ? 'bottom' : 'cross'; maze[x][y - 1] = maze[x][y - 1] === 'empty' ? 'bottom' : 'cross';
@ -183,7 +183,7 @@ export function genMaze(seed, complexity?) {
} }
//#region start digg //#region start digg
const nonVoidCells = []; const nonVoidCells: [number, number][] = [];
for (let y = 0; y < mazeSize; y++) { for (let y = 0; y < mazeSize; y++) {
for (let x = 0; x < mazeSize; x++) { for (let x = 0; x < mazeSize; x++) {
@ -199,7 +199,7 @@ export function genMaze(seed, complexity?) {
let hasEmptyCell = true; let hasEmptyCell = true;
while (hasEmptyCell) { while (hasEmptyCell) {
const nonEmptyCells = []; const nonEmptyCells: [number, number][] = [];
for (let y = 0; y < mazeSize; y++) { for (let y = 0; y < mazeSize; y++) {
for (let x = 0; x < mazeSize; x++) { for (let x = 0; x < mazeSize; x++) {

View file

@ -58,7 +58,7 @@ export default class extends Module {
@autobind @autobind
private async mentionHook(msg: Message) { private async mentionHook(msg: Message) {
if (msg.includes(['迷路'])) { if (msg.includes(['迷路'])) {
let size = null; let size: string | null = null;
if (msg.includes(['接待'])) size = 'veryEasy'; if (msg.includes(['接待'])) size = 'veryEasy';
if (msg.includes(['簡単', 'かんたん', '易しい', 'やさしい', '小さい', 'ちいさい'])) size = 'easy'; if (msg.includes(['簡単', 'かんたん', '易しい', 'やさしい', '小さい', 'ちいさい'])) size = 'easy';
if (msg.includes(['難しい', 'むずかしい', '複雑な', '大きい', 'おおきい'])) size = 'hard'; if (msg.includes(['難しい', 'むずかしい', '複雑な', '大きい', 'おおきい'])) size = 'hard';
@ -68,7 +68,7 @@ export default class extends Module {
setTimeout(async () => { setTimeout(async () => {
const file = await this.genMazeFile(Date.now(), size); const file = await this.genMazeFile(Date.now(), size);
this.log('Replying...'); this.log('Replying...');
msg.replyWithFile(serifs.maze.foryou, file); msg.reply(serifs.maze.foryou, { file });
}, 3000); }, 3000);
return { return {
reaction: 'like' reaction: 'like'

View file

@ -209,7 +209,7 @@ class Session {
*/ */
private onEnded = async (msg: any) => { private onEnded = async (msg: any) => {
// ストリームから切断 // ストリームから切断
process.send({ process.send!({
type: 'ended' type: 'ended'
}); });
@ -406,7 +406,7 @@ class Session {
console.timeEnd('think'); console.timeEnd('think');
setTimeout(() => { setTimeout(() => {
process.send({ process.send!({
type: 'put', type: 'put',
pos pos
}); });

View file

@ -43,7 +43,7 @@ export default class extends Module {
msg.reply(serifs.timer.set); msg.reply(serifs.timer.set);
const str = `${hours ? hoursQuery[0] : ''}${minutes ? minutesQuery[0] : ''}${seconds ? secondsQuery[0] : ''}`; const str = `${hours ? hoursQuery![0] : ''}${minutes ? minutesQuery![0] : ''}${seconds ? secondsQuery![0] : ''}`;
// タイマーセット // タイマーセット
this.setTimeoutWithPersistence(time, { this.setTimeoutWithPersistence(time, {
@ -59,6 +59,7 @@ export default class extends Module {
@autobind @autobind
private timeoutCallback(data) { private timeoutCallback(data) {
const friend = this.ai.lookupFriend(data.userId); const friend = this.ai.lookupFriend(data.userId);
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
const text = serifs.timer.notify(data.time, friend.name); const text = serifs.timer.notify(data.time, friend.name);
if (data.isDm) { if (data.isDm) {
this.ai.sendMessage(friend.userId, { this.ai.sendMessage(friend.userId, {

View file

@ -71,11 +71,11 @@ export default class Stream extends EventEmitter {
this.emit('_connected_'); this.emit('_connected_');
// バッファーを処理 // バッファーを処理
const _buffer = [].concat(this.buffer); // Shallow copy const _buffer = [...this.buffer]; // Shallow copy
this.buffer = []; // Clear buffer this.buffer = []; // Clear buffer
_buffer.forEach(data => { for (const data of _buffer) {
this.send(data); // Resend each buffered messages this.send(data); // Resend each buffered messages
}); }
// チャンネル再接続 // チャンネル再接続
if (isReconnect) { if (isReconnect) {
@ -107,7 +107,7 @@ export default class Stream extends EventEmitter {
if (type == 'channel') { if (type == 'channel') {
const id = body.id; const id = body.id;
let connections: Connection[]; let connections: (Connection | undefined)[];
connections = this.sharedConnections.filter(c => c.id === id); connections = this.sharedConnections.filter(c => c.id === id);
@ -115,10 +115,10 @@ export default class Stream extends EventEmitter {
connections = [this.nonSharedConnections.find(c => c.id === id)]; connections = [this.nonSharedConnections.find(c => c.id === id)];
} }
connections.filter(c => c != null).forEach(c => { for (const c of connections.filter(c => c != null)) {
c.emit(body.type, body.body); c!.emit(body.type, body.body);
c.emit('*', { type: body.type, body: body.body }); c!.emit('*', { type: body.type, body: body.body });
}); }
} else { } else {
this.emit(type, body); this.emit(type, body);
this.emit('*', { type, body }); this.emit('*', { type, body });

View file

@ -56,7 +56,7 @@ export function zenkakuToHankaku(str: string): string {
return str return str
.replace(reg, match => .replace(reg, match =>
kanaMap.find(x => x[0] == match)[1] kanaMap.find(x => x[0] == match)![1]
) )
.replace(/゛/g, '゙') .replace(/゛/g, '゙')
.replace(/゜/g, '゚'); .replace(/゜/g, '゚');
@ -72,7 +72,7 @@ export function hankakuToZenkaku(str: string): string {
return str return str
.replace(reg, match => .replace(reg, match =>
kanaMap.find(x => x[1] == match)[0] kanaMap.find(x => x[1] == match)![0]
) )
.replace(/゙/g, '゛') .replace(/゙/g, '゛')
.replace(/゚/g, '゜'); .replace(/゚/g, '゜');

View file

@ -40,7 +40,7 @@ export default function(text: string, words: (string | RegExp)[]): boolean {
} }
let textBefore = text; let textBefore = text;
let textAfter = null; let textAfter: string | null = null;
while (textBefore != textAfter) { while (textBefore != textAfter) {
textBefore = text; textBefore = text;

View file

@ -221,11 +221,11 @@ export const and = [
'のそばにある', 'のそばにある',
]; ];
export function genItem(seedOrRng = null) { export function genItem(seedOrRng?: (() => number) | string | number) {
const rng = seedOrRng const rng = seedOrRng
? typeof seedOrRng === 'function' ? typeof seedOrRng === 'function'
? seedOrRng ? seedOrRng
: seedrandom(seedOrRng) : seedrandom(seedOrRng.toString())
: Math.random; : Math.random;
let item = ''; let item = '';

View file

@ -5,7 +5,7 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"strictNullChecks": false, "strictNullChecks": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"sourceMap": false, "sourceMap": false,
"target": "es2017", "target": "es2017",