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",
"scripts": {
"start": "node ./built",

View file

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

View file

@ -8,7 +8,7 @@ import { genItem } from './vocabulary';
export type FriendDoc = {
userId: string;
user: User;
name?: string;
name?: string | null;
love?: number;
lastLoveIncrementedAt?: string;
todayLoveIncrements?: number;
@ -42,21 +42,30 @@ export default class Friend {
this.ai = ai;
if (opts.user) {
this.doc = this.ai.friends.findOne({
const exist = this.ai.friends.findOne({
userId: opts.user.id
});
if (this.doc == null) {
this.doc = this.ai.friends.insertOne({
if (exist == null) {
const inserted = this.ai.friends.insertOne({
userId: opts.user.id,
user: opts.user
});
if (inserted == null) {
throw new Error('Failed to insert friend doc');
}
this.doc = inserted;
} else {
this.doc = exist;
this.doc.user = opts.user;
this.save();
}
} else {
} else if (opts.doc) {
this.doc = opts.doc;
} else {
throw new Error('No friend info specified');
}
}
@ -100,7 +109,7 @@ export default class Friend {
}
// 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;
this.doc.love++;

View file

@ -7,6 +7,7 @@ const promiseRetry = require('promise-retry');
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';
@ -39,7 +40,7 @@ function log(msg: string): void {
_log(`[Boot]: ${msg}`);
}
log(chalk.bold('Ai v1.0'));
log(chalk.bold(`Ai v${pkg._v}`));
promiseRetry(retry => {
log(`Account fetching... ${chalk.gray(config.host)}`);

View file

@ -60,47 +60,32 @@ export default class Message {
}
@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;
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
});
} else {
return await this.ai.post({
replyId: this.messageOrNote.id,
text: text,
cw: cw,
renoteId: renote
});
if (!opts?.immediate) {
await delay(2000);
}
}
@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
fileId: opts?.file?.id
});
} else {
return await this.ai.post({
replyId: this.messageOrNote.id,
text: text,
fileIds: [file.id],
cw: cw,
renoteId: renote
fileIds: opts?.file ? [opts?.file.id] : undefined,
cw: opts?.cw,
renoteId: opts?.renote
});
}
}

View file

@ -37,7 +37,7 @@ export default abstract class Module {
* @param data
*/
@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);
}
@ -46,7 +46,7 @@ export default abstract class Module {
* @param key
*/
@autobind
protected unsubscribeReply(key: string) {
protected unsubscribeReply(key: string | null) {
this.ai.unsubscribeReply(this, key);
}

View file

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

View file

@ -96,7 +96,7 @@ export function renderChart(chart: Chart) {
ctx.fillText(step.toString(), chartAreaX, chartAreaY + y - 8);
}
const newDatasets = [];
const newDatasets: any[] = [];
for (let series = 0; series < serieses; series++) {
newDatasets.push({
@ -127,7 +127,7 @@ export function renderChart(chart: Chart) {
ctx.lineCap = 'round';
for (let xAxis = 0; xAxis < xAxisCount; xAxis++) {
const xAxisPerTypeHeights = [];
const xAxisPerTypeHeights: number[] = [];
for (let series = 0; series < serieses; series++) {
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
// encompass the Y values.
const steps = [];
const steps: number[] = [];
// Determine Range
const range = upperBound - lowerBound;

View file

@ -25,7 +25,8 @@ export default class extends Module {
this.transferBegin(msg) ||
this.transferEnd(msg) ||
this.setName(msg) ||
this.modules(msg)
this.modules(msg) ||
this.version(msg)
);
}
@ -79,7 +80,7 @@ export default class extends Module {
return true;
}
const name = msg.text.match(/^(.+?)って呼んで/)[1];
const name = msg.text.match(/^(.+?)って呼んで/)![1];
if (name.length > 10) {
msg.reply(serifs.core.tooLong);
@ -120,7 +121,21 @@ export default class extends Module {
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;
}

View file

@ -37,7 +37,9 @@ export default class extends Module {
const rng = seedrandom(seed);
const omikuji = blessing[Math.floor(rng() * blessing.length)];
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;
} else {
return false;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -43,7 +43,7 @@ export default class extends Module {
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, {
@ -59,6 +59,7 @@ export default class extends Module {
@autobind
private timeoutCallback(data) {
const friend = this.ai.lookupFriend(data.userId);
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
const text = serifs.timer.notify(data.time, friend.name);
if (data.isDm) {
this.ai.sendMessage(friend.userId, {

View file

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

View file

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

View file

@ -40,7 +40,7 @@ export default function(text: string, words: (string | RegExp)[]): boolean {
}
let textBefore = text;
let textAfter = null;
let textAfter: string | null = null;
while (textBefore != textAfter) {
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
? typeof seedOrRng === 'function'
? seedOrRng
: seedrandom(seedOrRng)
: seedrandom(seedOrRng.toString())
: Math.random;
let item = '';

View file

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