ai/src/modules/reminder/index.ts

178 lines
4.5 KiB
TypeScript

import autobind from 'autobind-decorator';
import * as loki from 'lokijs';
import Module from '@/module';
import Message from '@/message';
import serifs, { getSerif } from '@/serifs';
import { acct } from '@/utils/acct';
import config from '@/config';
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.startsWith('reminds') || text.startsWith('todos')) {
const reminds = this.reminds.find({
userId: msg.userId,
});
const getQuoteLink = id => `[${id}](${config.host}/notes/${id})`;
msg.reply(serifs.reminder.reminds + '\n' + reminds.map(remind => `${remind.thing ? remind.thing : getQuoteLink(remind.quoteId)}`).join('\n'));
return true;
}
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.visibility === 'followers') {
msg.reply(serifs.reminder.invalid);
return {
reaction: '🆖',
immediate: 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(),
});
// メンションをsubscribe
this.subscribeReply(remind!.id, msg.isDm, msg.isDm ? msg.userId : msg.id, {
id: remind!.id
});
if (msg.quoteId) {
// 引用元をsubscribe
this.subscribeReply(remind!.id, false, msg.quoteId, {
id: remind!.id
});
}
// タイマーセット
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
id: remind!.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(['やめる', 'やめた', 'キャンセル']);
const isOneself = msg.userId === remind.userId;
if ((done || cancel) && isOneself) {
this.unsubscribeReply(key);
this.reminds.remove(remind);
msg.reply(done ? getSerif(serifs.reminder.done(msg.friend.name)) : serifs.reminder.cancel);
return;
} else if (isOneself === false) {
msg.reply(serifs.reminder.doneFromInvalidUser);
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 {
try {
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)
});
} catch (err) {
// renote対象が消されていたらリマインダー解除
if (err.statusCode === 400 && err.error.error.message === 'No such renote target.') {
this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id);
this.reminds.remove(remind);
return;
}
return;
}
}
this.subscribeReply(remind.id, remind.isDm, remind.isDm ? remind.userId : reply.id, {
id: remind.id
});
// タイマーセット
this.setTimeoutWithPersistence(NOTIFY_INTERVAL, {
id: remind.id,
});
}
}