mirror of
https://github.com/syuilo/ai.git
synced 2024-11-24 22:01:07 +00:00
nanka iroiro
This commit is contained in:
parent
bc1c115de4
commit
ec681a6705
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "ai",
|
||||
"_v": "1.1",
|
||||
"main": "./built/index.js",
|
||||
"scripts": {
|
||||
"start": "node ./built",
|
||||
|
|
14
src/ai.ts
14
src/ai.ts
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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)}`);
|
||||
|
|
|
@ -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)}`);
|
||||
|
||||
if (!opts?.immediate) {
|
||||
await delay(2000);
|
||||
}
|
||||
|
||||
if (this.isDm) {
|
||||
return await this.ai.sendMessage(this.messageOrNote.userId, {
|
||||
text: text
|
||||
text: text,
|
||||
fileId: opts?.file?.id
|
||||
});
|
||||
} else {
|
||||
return await this.ai.post({
|
||||
replyId: this.messageOrNote.id,
|
||||
text: text,
|
||||
cw: cw,
|
||||
renoteId: 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
|
||||
fileIds: opts?.file ? [opts?.file.id] : undefined,
|
||||
cw: opts?.cw,
|
||||
renoteId: opts?.renote
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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, '゜');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = '';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
"target": "es2017",
|
||||
|
|
Loading…
Reference in a new issue