mirror of
https://github.com/syuilo/ai.git
synced 2024-11-12 17:08:00 +00:00
リマインダーとか
This commit is contained in:
parent
741feb86d1
commit
31449bd398
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_v": "1.3.0",
|
||||
"_v": "1.4.0",
|
||||
"main": "./built/index.js",
|
||||
"scripts": {
|
||||
"start": "node ./built",
|
||||
|
|
42
src/ai.ts
42
src/ai.ts
|
@ -18,11 +18,12 @@ 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 ContextHook = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>;
|
||||
type TimeoutCallback = (data?: any) => void;
|
||||
|
||||
export type HandlerResult = {
|
||||
reaction: string | null;
|
||||
reaction?: string | null;
|
||||
immediate?: boolean;
|
||||
};
|
||||
|
||||
export type InstallerResult = {
|
||||
|
@ -220,18 +221,10 @@ export default class 藍 {
|
|||
});
|
||||
|
||||
let reaction: string | null = 'love';
|
||||
let immediate: boolean = false;
|
||||
|
||||
//#region
|
||||
// コンテキストがあればコンテキストフック呼び出し
|
||||
// なければそれぞれのモジュールについてフックが引っかかるまで呼び出し
|
||||
if (context != null) {
|
||||
const handler = this.contextHooks[context.module];
|
||||
const res = await handler(msg, context.data);
|
||||
|
||||
if (res != null && typeof res === 'object') {
|
||||
reaction = res.reaction;
|
||||
}
|
||||
} else {
|
||||
const invokeMentionHooks = async () => {
|
||||
let res: boolean | HandlerResult | null = null;
|
||||
|
||||
for (const handler of this.mentionHooks) {
|
||||
|
@ -240,12 +233,33 @@ export default class 藍 {
|
|||
}
|
||||
|
||||
if (res != null && typeof res === 'object') {
|
||||
reaction = res.reaction;
|
||||
if (res.reaction != null) reaction = res.reaction;
|
||||
if (res.immediate != null) immediate = res.immediate;
|
||||
}
|
||||
};
|
||||
|
||||
// コンテキストがあればコンテキストフック呼び出し
|
||||
// なければそれぞれのモジュールについてフックが引っかかるまで呼び出し
|
||||
if (context != null) {
|
||||
const handler = this.contextHooks[context.module];
|
||||
const res = await handler(context.key, msg, context.data);
|
||||
|
||||
if (res != null && typeof res === 'object') {
|
||||
if (res.reaction != null) reaction = res.reaction;
|
||||
if (res.immediate != null) immediate = res.immediate;
|
||||
}
|
||||
|
||||
if (res === false) {
|
||||
await invokeMentionHooks();
|
||||
}
|
||||
} else {
|
||||
await invokeMentionHooks();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await delay(1000);
|
||||
if (!immediate) {
|
||||
await delay(1000);
|
||||
}
|
||||
|
||||
if (msg.isDm) {
|
||||
// 既読にする
|
||||
|
|
|
@ -33,6 +33,7 @@ import ChartModule from './modules/chart';
|
|||
import SleepReportModule from './modules/sleep-report';
|
||||
import NotingModule from './modules/noting';
|
||||
import PollModule from './modules/poll';
|
||||
import ReminderModule from './modules/reminder';
|
||||
|
||||
console.log(' __ ____ _____ ___ ');
|
||||
console.log(' /__\\ (_ _)( _ )/ __)');
|
||||
|
@ -86,6 +87,7 @@ promiseRetry(retry => {
|
|||
new SleepReportModule(),
|
||||
new NotingModule(),
|
||||
new PollModule(),
|
||||
new ReminderModule(),
|
||||
]);
|
||||
}).catch(e => {
|
||||
log(chalk.red('Failed to fetch the account'));
|
||||
|
|
|
@ -30,6 +30,13 @@ export default class Message {
|
|||
return this.messageOrNote.text;
|
||||
}
|
||||
|
||||
public get quoteId(): string | null {
|
||||
return this.messageOrNote.renoteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* メンション部分を除いたテキスト本文
|
||||
*/
|
||||
public get extractedText(): string {
|
||||
const host = new URL(config.host).host.replace(/\./g, '\\.');
|
||||
return this.text
|
||||
|
|
|
@ -141,12 +141,12 @@ export default class extends Module {
|
|||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(msg: Message, data: any) {
|
||||
private async contextHook(key: any, msg: Message, data: any) {
|
||||
if (msg.text == null) return;
|
||||
|
||||
const done = () => {
|
||||
msg.reply(serifs.core.setNameOk(msg.friend.name));
|
||||
this.unsubscribeReply(msg.userId);
|
||||
this.unsubscribeReply(key);
|
||||
};
|
||||
|
||||
if (msg.text.includes('はい')) {
|
||||
|
|
|
@ -66,7 +66,7 @@ export default class extends Module {
|
|||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(msg: Message) {
|
||||
private async contextHook(key: any, msg: Message) {
|
||||
if (msg.text == null) return;
|
||||
|
||||
const exist = this.guesses.findOne({
|
||||
|
@ -76,7 +76,7 @@ export default class extends Module {
|
|||
|
||||
// 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
if (exist == null) {
|
||||
this.unsubscribeReply(msg.userId);
|
||||
this.unsubscribeReply(key);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ export default class extends Module {
|
|||
exist.isEnded = true;
|
||||
exist.endedAt = Date.now();
|
||||
this.guesses.update(exist);
|
||||
this.unsubscribeReply(msg.userId);
|
||||
this.unsubscribeReply(key);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ export default class extends Module {
|
|||
if (end) {
|
||||
exist.isEnded = true;
|
||||
exist.endedAt = Date.now();
|
||||
this.unsubscribeReply(msg.userId);
|
||||
this.unsubscribeReply(key);
|
||||
}
|
||||
|
||||
this.guesses.update(exist);
|
||||
|
|
|
@ -4,6 +4,7 @@ import Module from '@/module';
|
|||
import Message from '@/message';
|
||||
import serifs from '@/serifs';
|
||||
import { User } from '@/misskey/user';
|
||||
import { acct } from '@/utils/acct';
|
||||
|
||||
type Game = {
|
||||
votes: {
|
||||
|
@ -82,7 +83,7 @@ export default class extends Module {
|
|||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(msg: Message) {
|
||||
private async contextHook(key: any, msg: Message) {
|
||||
if (msg.text == null) return {
|
||||
reaction: 'hmm'
|
||||
};
|
||||
|
@ -172,12 +173,6 @@ export default class extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
function acct(user: Game['votes'][0]['user']): string {
|
||||
return user.host
|
||||
? `@${user.username}@${user.host}`
|
||||
: `@${user.username}`;
|
||||
}
|
||||
|
||||
let results: string[] = [];
|
||||
let winner: Game['votes'][0]['user'] | null = null;
|
||||
|
||||
|
|
140
src/modules/reminder/index.ts
Normal file
140
src/modules/reminder/index.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import autobind from 'autobind-decorator';
|
||||
import * as loki from 'lokijs';
|
||||
import Module from '@/module';
|
||||
import Message from '@/message';
|
||||
import serifs from '@/serifs';
|
||||
import { acct } from '@/utils/acct';
|
||||
|
||||
const NOTIFY_INTERVAL = 1000 * 60 * 60 * 12;
|
||||
|
||||
export default class extends Module {
|
||||
public readonly name = 'reminder';
|
||||
|
||||
private reminds: loki.Collection<{
|
||||
userId: string;
|
||||
id: string;
|
||||
isDm: boolean;
|
||||
thing: string | null;
|
||||
quoteId: string | null;
|
||||
times: number; // 催促した回数(使うのか?)
|
||||
createdAt: number;
|
||||
}>;
|
||||
|
||||
@autobind
|
||||
public install() {
|
||||
this.reminds = this.ai.getCollection('reminds', {
|
||||
indices: ['userId', 'id']
|
||||
});
|
||||
|
||||
return {
|
||||
mentionHook: this.mentionHook,
|
||||
contextHook: this.contextHook,
|
||||
timeoutCallback: this.timeoutCallback,
|
||||
};
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async mentionHook(msg: Message) {
|
||||
let text = msg.extractedText.toLowerCase();
|
||||
if (!text.startsWith('remind') && !text.startsWith('todo')) return false;
|
||||
if (text.match(/^(.+?)\s(.+)/)) {
|
||||
text = text.replace(/^(.+?)\s/, '');
|
||||
} else {
|
||||
text = '';
|
||||
}
|
||||
|
||||
const separatorIndex = text.indexOf(' ') > -1 ? text.indexOf(' ') : text.indexOf('\n');
|
||||
const thing = text.substr(separatorIndex + 1).trim();
|
||||
|
||||
if (thing === '' && msg.quoteId == null) {
|
||||
msg.reply(serifs.reminder.invalid);
|
||||
return true;
|
||||
}
|
||||
|
||||
const remind = this.reminds.insertOne({
|
||||
id: msg.id,
|
||||
userId: msg.userId,
|
||||
isDm: msg.isDm,
|
||||
thing: thing === '' ? null : thing,
|
||||
quoteId: msg.quoteId,
|
||||
times: 0,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
|
||||
this.subscribeReply(msg.id, msg.isDm, msg.isDm ? msg.userId : msg.id, {
|
||||
id: remind!.id
|
||||
});
|
||||
|
||||
// タイマーセット
|
||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||
id: msg.id,
|
||||
});
|
||||
|
||||
return {
|
||||
reaction: '🆗',
|
||||
immediate: true,
|
||||
};
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async contextHook(key: any, msg: Message, data: any) {
|
||||
if (msg.text == null) return;
|
||||
|
||||
const remind = this.reminds.findOne({
|
||||
id: data.id,
|
||||
});
|
||||
|
||||
if (remind == null) {
|
||||
this.unsubscribeReply(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const done = msg.includes(['done', 'やった']);
|
||||
const cancel = msg.includes(['やめる']);
|
||||
|
||||
if (done || cancel) {
|
||||
this.unsubscribeReply(key);
|
||||
this.reminds.remove(remind);
|
||||
msg.reply(done ? serifs.reminder.done(msg.friend.name) : serifs.reminder.cancel);
|
||||
return;
|
||||
} else {
|
||||
if (msg.isDm) this.unsubscribeReply(key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async timeoutCallback(data) {
|
||||
const remind = this.reminds.findOne({
|
||||
id: data.id
|
||||
});
|
||||
if (remind == null) return;
|
||||
|
||||
remind.times++;
|
||||
this.reminds.update(remind);
|
||||
|
||||
const friend = this.ai.lookupFriend(remind.userId);
|
||||
if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||
|
||||
let reply;
|
||||
if (remind.isDm) {
|
||||
this.ai.sendMessage(friend.userId, {
|
||||
text: serifs.reminder.notifyWithThing(remind.thing, friend.name)
|
||||
});
|
||||
} else {
|
||||
reply = await this.ai.post({
|
||||
renoteId: remind.thing == null && remind.quoteId ? remind.quoteId : remind.id,
|
||||
text: acct(friend.doc.user) + ' ' + serifs.reminder.notify(friend.name)
|
||||
});
|
||||
}
|
||||
|
||||
this.subscribeReply(remind.id, remind.isDm, remind.isDm ? remind.userId : reply.id, {
|
||||
id: remind.id
|
||||
});
|
||||
|
||||
// タイマーセット
|
||||
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
|
||||
id: remind.id,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -335,6 +335,21 @@ export default {
|
|||
notify: (time, name) => name ? `${name}、${time}経ちましたよ!` : `${time}経ちましたよ!`
|
||||
},
|
||||
|
||||
/**
|
||||
* リマインダー
|
||||
*/
|
||||
reminder: {
|
||||
invalid: 'うーん...?',
|
||||
|
||||
notify: (name) => name ? `${name}、これやりましたか?` : `これやりましたか?`,
|
||||
|
||||
notifyWithThing: (thing, name) => name ? `${name}、「${thing}」やりましたか?` : `「${thing}」やりましたか?`,
|
||||
|
||||
done: (name) => name ? `よく出来ました、${name}♪` : `よく出来ました♪`,
|
||||
|
||||
cancel: `わかりました。`,
|
||||
},
|
||||
|
||||
/**
|
||||
* バレンタイン
|
||||
*/
|
||||
|
|
5
src/utils/acct.ts
Normal file
5
src/utils/acct.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export function acct(user: { username: string; host?: string | null; }): string {
|
||||
return user.host
|
||||
? `@${user.username}@${user.host}`
|
||||
: `@${user.username}`;
|
||||
}
|
|
@ -3,8 +3,8 @@ import { katakanaToHiragana, hankakuToZenkaku } from './japanese';
|
|||
export default function(text: string, words: string[]): boolean {
|
||||
if (text == null) return false;
|
||||
|
||||
text = katakanaToHiragana(hankakuToZenkaku(text));
|
||||
words = words.map(word => katakanaToHiragana(word));
|
||||
text = katakanaToHiragana(hankakuToZenkaku(text)).toLowerCase();
|
||||
words = words.map(word => katakanaToHiragana(word).toLowerCase());
|
||||
|
||||
return words.some(word => text.includes(word));
|
||||
}
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
### タイマー
|
||||
指定した時間、分、秒を経過したら教えてくれます。「3分40秒」のように単位を混ぜることもできます。
|
||||
|
||||
### リマインダー
|
||||
```
|
||||
@ai remind 部屋の掃除
|
||||
```
|
||||
のようにメンションを飛ばすと12時間置きに催促されます。その飛ばしたメンションか、藍ちゃんからの催促に「やった」と返信することでリマインダー解除されます。
|
||||
また、引用Renoteでメンションすることもできます。
|
||||
|
||||
### 福笑い
|
||||
藍に「絵文字」と言うと、藍が考えた絵文字の組み合わせを教えてくれます。
|
||||
|
||||
|
|
Loading…
Reference in a new issue