mirror of
https://github.com/syuilo/ai.git
synced 2024-11-22 05: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",
|
"main": "./built/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./built",
|
"start": "node ./built",
|
||||||
|
|
40
src/ai.ts
40
src/ai.ts
|
@ -18,11 +18,12 @@ import log from '@/utils/log';
|
||||||
const pkg = require('../package.json');
|
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 = (key: any, msg: Message, data?: any) => Promise<void | boolean | HandlerResult>;
|
||||||
type TimeoutCallback = (data?: any) => void;
|
type TimeoutCallback = (data?: any) => void;
|
||||||
|
|
||||||
export type HandlerResult = {
|
export type HandlerResult = {
|
||||||
reaction: string | null;
|
reaction?: string | null;
|
||||||
|
immediate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InstallerResult = {
|
export type InstallerResult = {
|
||||||
|
@ -220,18 +221,10 @@ export default class 藍 {
|
||||||
});
|
});
|
||||||
|
|
||||||
let reaction: string | null = 'love';
|
let reaction: string | null = 'love';
|
||||||
|
let immediate: boolean = false;
|
||||||
|
|
||||||
//#region
|
//#region
|
||||||
// コンテキストがあればコンテキストフック呼び出し
|
const invokeMentionHooks = async () => {
|
||||||
// なければそれぞれのモジュールについてフックが引っかかるまで呼び出し
|
|
||||||
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 {
|
|
||||||
let res: boolean | HandlerResult | null = null;
|
let res: boolean | HandlerResult | null = null;
|
||||||
|
|
||||||
for (const handler of this.mentionHooks) {
|
for (const handler of this.mentionHooks) {
|
||||||
|
@ -240,12 +233,33 @@ export default class 藍 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res != null && typeof res === 'object') {
|
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
|
//#endregion
|
||||||
|
|
||||||
|
if (!immediate) {
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.isDm) {
|
if (msg.isDm) {
|
||||||
// 既読にする
|
// 既読にする
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ChartModule from './modules/chart';
|
||||||
import SleepReportModule from './modules/sleep-report';
|
import SleepReportModule from './modules/sleep-report';
|
||||||
import NotingModule from './modules/noting';
|
import NotingModule from './modules/noting';
|
||||||
import PollModule from './modules/poll';
|
import PollModule from './modules/poll';
|
||||||
|
import ReminderModule from './modules/reminder';
|
||||||
|
|
||||||
console.log(' __ ____ _____ ___ ');
|
console.log(' __ ____ _____ ___ ');
|
||||||
console.log(' /__\\ (_ _)( _ )/ __)');
|
console.log(' /__\\ (_ _)( _ )/ __)');
|
||||||
|
@ -86,6 +87,7 @@ promiseRetry(retry => {
|
||||||
new SleepReportModule(),
|
new SleepReportModule(),
|
||||||
new NotingModule(),
|
new NotingModule(),
|
||||||
new PollModule(),
|
new PollModule(),
|
||||||
|
new ReminderModule(),
|
||||||
]);
|
]);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
log(chalk.red('Failed to fetch the account'));
|
log(chalk.red('Failed to fetch the account'));
|
||||||
|
|
|
@ -30,6 +30,13 @@ export default class Message {
|
||||||
return this.messageOrNote.text;
|
return this.messageOrNote.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get quoteId(): string | null {
|
||||||
|
return this.messageOrNote.renoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* メンション部分を除いたテキスト本文
|
||||||
|
*/
|
||||||
public get extractedText(): string {
|
public get extractedText(): string {
|
||||||
const host = new URL(config.host).host.replace(/\./g, '\\.');
|
const host = new URL(config.host).host.replace(/\./g, '\\.');
|
||||||
return this.text
|
return this.text
|
||||||
|
|
|
@ -141,12 +141,12 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async contextHook(msg: Message, data: any) {
|
private async contextHook(key: any, msg: Message, data: any) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return;
|
||||||
|
|
||||||
const done = () => {
|
const done = () => {
|
||||||
msg.reply(serifs.core.setNameOk(msg.friend.name));
|
msg.reply(serifs.core.setNameOk(msg.friend.name));
|
||||||
this.unsubscribeReply(msg.userId);
|
this.unsubscribeReply(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (msg.text.includes('はい')) {
|
if (msg.text.includes('はい')) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async contextHook(msg: Message) {
|
private async contextHook(key: any, msg: Message) {
|
||||||
if (msg.text == null) return;
|
if (msg.text == null) return;
|
||||||
|
|
||||||
const exist = this.guesses.findOne({
|
const exist = this.guesses.findOne({
|
||||||
|
@ -76,7 +76,7 @@ export default class extends Module {
|
||||||
|
|
||||||
// 処理の流れ上、実際にnullになることは無さそうだけど一応
|
// 処理の流れ上、実際にnullになることは無さそうだけど一応
|
||||||
if (exist == null) {
|
if (exist == null) {
|
||||||
this.unsubscribeReply(msg.userId);
|
this.unsubscribeReply(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ export default class extends Module {
|
||||||
exist.isEnded = true;
|
exist.isEnded = true;
|
||||||
exist.endedAt = Date.now();
|
exist.endedAt = Date.now();
|
||||||
this.guesses.update(exist);
|
this.guesses.update(exist);
|
||||||
this.unsubscribeReply(msg.userId);
|
this.unsubscribeReply(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ export default class extends Module {
|
||||||
if (end) {
|
if (end) {
|
||||||
exist.isEnded = true;
|
exist.isEnded = true;
|
||||||
exist.endedAt = Date.now();
|
exist.endedAt = Date.now();
|
||||||
this.unsubscribeReply(msg.userId);
|
this.unsubscribeReply(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.guesses.update(exist);
|
this.guesses.update(exist);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Module from '@/module';
|
||||||
import Message from '@/message';
|
import Message from '@/message';
|
||||||
import serifs from '@/serifs';
|
import serifs from '@/serifs';
|
||||||
import { User } from '@/misskey/user';
|
import { User } from '@/misskey/user';
|
||||||
|
import { acct } from '@/utils/acct';
|
||||||
|
|
||||||
type Game = {
|
type Game = {
|
||||||
votes: {
|
votes: {
|
||||||
|
@ -82,7 +83,7 @@ export default class extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async contextHook(msg: Message) {
|
private async contextHook(key: any, msg: Message) {
|
||||||
if (msg.text == null) return {
|
if (msg.text == null) return {
|
||||||
reaction: 'hmm'
|
reaction: 'hmm'
|
||||||
};
|
};
|
||||||
|
@ -172,12 +173,6 @@ export default class extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function acct(user: Game['votes'][0]['user']): string {
|
|
||||||
return user.host
|
|
||||||
? `@${user.username}@${user.host}`
|
|
||||||
: `@${user.username}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let results: string[] = [];
|
let results: string[] = [];
|
||||||
let winner: Game['votes'][0]['user'] | null = null;
|
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}経ちましたよ!`
|
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 {
|
export default function(text: string, words: string[]): boolean {
|
||||||
if (text == null) return false;
|
if (text == null) return false;
|
||||||
|
|
||||||
text = katakanaToHiragana(hankakuToZenkaku(text));
|
text = katakanaToHiragana(hankakuToZenkaku(text)).toLowerCase();
|
||||||
words = words.map(word => katakanaToHiragana(word));
|
words = words.map(word => katakanaToHiragana(word).toLowerCase());
|
||||||
|
|
||||||
return words.some(word => text.includes(word));
|
return words.some(word => text.includes(word));
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,13 @@
|
||||||
### タイマー
|
### タイマー
|
||||||
指定した時間、分、秒を経過したら教えてくれます。「3分40秒」のように単位を混ぜることもできます。
|
指定した時間、分、秒を経過したら教えてくれます。「3分40秒」のように単位を混ぜることもできます。
|
||||||
|
|
||||||
|
### リマインダー
|
||||||
|
```
|
||||||
|
@ai remind 部屋の掃除
|
||||||
|
```
|
||||||
|
のようにメンションを飛ばすと12時間置きに催促されます。その飛ばしたメンションか、藍ちゃんからの催促に「やった」と返信することでリマインダー解除されます。
|
||||||
|
また、引用Renoteでメンションすることもできます。
|
||||||
|
|
||||||
### 福笑い
|
### 福笑い
|
||||||
藍に「絵文字」と言うと、藍が考えた絵文字の組み合わせを教えてくれます。
|
藍に「絵文字」と言うと、藍が考えた絵文字の組み合わせを教えてくれます。
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue