From f43aac7291a087dbebf5b12a5e55a4569b4c5136 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 26 Mar 2025 08:58:05 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88=E5=BE=A9?= =?UTF-8?q?=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ai.ts | 94 ++++++++++++++++++++++-------- src/message.ts | 56 +++++++++--------- src/module.ts | 7 ++- src/modules/core/index.ts | 4 +- src/modules/guessing-game/index.ts | 6 +- src/modules/kazutori/index.ts | 2 +- src/modules/reminder/index.ts | 35 ++++++----- src/modules/timer/index.ts | 15 +++-- 8 files changed, 144 insertions(+), 75 deletions(-) diff --git a/src/ai.ts b/src/ai.ts index ecb7e30..cc61bcb 100644 --- a/src/ai.ts +++ b/src/ai.ts @@ -54,6 +54,7 @@ export default class 藍 { private meta: loki.Collection; private contexts: loki.Collection<{ + isChat: boolean; noteId?: string; userId?: string; module: string; @@ -146,7 +147,7 @@ export default class 藍 { if (data.text && data.text.startsWith('@' + this.account.username)) { // Misskeyのバグで投稿が非公開扱いになる if (data.text == null) data = await this.api('notes/show', { noteId: data.id }); - this.onReceiveMessage(new Message(this, data)); + this.onReceiveMessage(new Message(this, data, false)); } }); @@ -156,7 +157,7 @@ export default class 藍 { if (data.text && data.text.startsWith('@' + this.account.username)) return; // Misskeyのバグで投稿が非公開扱いになる if (data.text == null) data = await this.api('notes/show', { noteId: data.id }); - this.onReceiveMessage(new Message(this, data)); + this.onReceiveMessage(new Message(this, data, false)); }); // Renoteされたとき @@ -171,16 +172,48 @@ export default class 藍 { }); }); - // メッセージ - mainStream.on('messagingMessage', data => { - if (data.userId == this.account.id) return; // 自分は弾く - this.onReceiveMessage(new Message(this, data)); - }); - // 通知 mainStream.on('notification', data => { this.onNotification(data); }); + + // チャット + mainStream.on('newChatMessage', data => { + const fromUser = data.fromUser; + if (data.fromUserId == this.account.id) return; // 自分は弾く + this.onReceiveMessage(new Message(this, data, true)); + + // 一定期間 chatUser / chatRoom のストリームに接続して今後のやり取りに備える + if (data.fromUserId) { + const chatStream = this.connection.connectToChannel('chatUser', { + otherId: data.fromUserId, + }); + + let timer; + function setTimer() { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + chatStream.dispose(); + }, 1000 * 60 * 2); + } + setTimer(); + + chatStream.on('message', (data) => { + if (data.fromUserId == this.account.id) return; // 自分は弾く + chatStream.send('read', { + id: data.id, + }); + this.onReceiveMessage(new Message(this, { + ...data, + // fromUserは省略されてくるため + fromUser: fromUser, + }, true)); + setTimer(); + }); + } else { + // TODO: room + } + }); //#endregion // Install modules @@ -218,10 +251,14 @@ export default class 藍 { return; } - const isNoContext = msg.replyId == null; + const isNoContext = !msg.isChat && msg.replyId == null; // Look up the context - const context = isNoContext ? null : this.contexts.findOne({ + const context = isNoContext ? null : this.contexts.findOne(msg.isChat ? { + isChat: true, + userId: msg.userId + } : { + isChat: false, noteId: msg.replyId }); @@ -266,12 +303,16 @@ export default class 藍 { await sleep(1000); } - // リアクションする - if (reaction) { - this.api('notes/reactions/create', { - noteId: msg.id, - reaction: reaction - }); + if (msg.isChat) { + // TODO: リアクション? + } else { + // リアクションする + if (reaction) { + this.api('notes/reactions/create', { + noteId: msg.id, + reaction: reaction + }); + } } } @@ -366,13 +407,12 @@ export default class 藍 { } /** - * 指定ユーザーにトークメッセージを送信します + * 指定ユーザーにチャットメッセージを送信します */ @bindThis public sendMessage(userId: any, param: any) { - return this.post(Object.assign({ - visibility: 'specified', - visibleUserIds: [userId], + return this.api('chat/messages/create-to-user', Object.assign({ + toUserId: userId, }, param)); } @@ -393,12 +433,20 @@ export default class 藍 { * コンテキストを生成し、ユーザーからの返信を待ち受けます * @param module 待ち受けるモジュール名 * @param key コンテキストを識別するためのキー - * @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID + * @param isChat チャット上のコンテキストかどうか + * @param id チャット上のコンテキストならばチャット相手のID、そうでないなら待ち受ける投稿のID * @param data コンテキストに保存するオプションのデータ */ @bindThis - public subscribeReply(module: Module, key: string | null, id: string, data?: any) { - this.contexts.insertOne({ + public subscribeReply(module: Module, key: string | null, isChat: boolean, id: string, data?: any) { + this.contexts.insertOne(isChat ? { + isChat: true, + userId: id, + module: module.name, + key: key, + data: data + } : { + isChat: false, noteId: id, module: module.name, key: key, diff --git a/src/message.ts b/src/message.ts index a9ea364..9822968 100644 --- a/src/message.ts +++ b/src/message.ts @@ -11,30 +11,36 @@ import { sleep } from '@/utils/sleep.js'; export default class Message { private ai: 藍; - private note: any; + private chatMessage: { id: string; fromUser: any; fromUserId: string; text: string; } | null; + private note: { id: string; user: any; userId: string; text: string; renoteId: string; replyId: string; } | null; + public isChat: boolean; public get id(): string { - return this.note.id; + return this.chatMessage ? this.chatMessage.id : this.note.id; } public get user(): User { - return this.note.user; + return this.chatMessage ? this.chatMessage.fromUser : this.note.user; } public get userId(): string { - return this.note.userId; + return this.chatMessage ? this.chatMessage.fromUserId : this.note.userId; } public get text(): string { - return this.note.text; + return this.chatMessage ? this.chatMessage.text : this.note.text; } public get quoteId(): string | null { - return this.note.renoteId; + return this.chatMessage ? null : this.note.renoteId; } - public get visibility(): string { - return this.note.visibility; + public get replyId(): string | null { + return this.chatMessage ? null : this.note.replyId; + } + + public get visibility(): string | null { + return this.chatMessage ? null : this.note.visibility; } /** @@ -48,15 +54,13 @@ export default class Message { .trim(); } - public get replyId(): string { - return this.note.replyId; - } - public friend: Friend; - constructor(ai: 藍, note: any) { + constructor(ai: 藍, chatMessageOrNote: any, isChat: boolean) { this.ai = ai; - this.note = note; + this.chatMessage = isChat ? chatMessageOrNote : null; + this.note = isChat ? null : chatMessageOrNote; + this.isChat = isChat; this.friend = new Friend(ai, { user: this.user }); @@ -83,19 +87,19 @@ export default class Message { await sleep(2000); } - const postData = { - replyId: this.note.id, - text: text, - fileIds: opts?.file ? [opts?.file.id] : undefined, - cw: opts?.cw, - renoteId: opts?.renote - }; - - // DM以外は普通に返信し、DMの場合はDMで返信する - if (this.note.visibility != 'specified') { - return await this.ai.post(postData); + if (this.chatMessage) { + return await this.ai.sendMessage(this.chatMessage.fromUserId, { + text: text, + fileId: opts?.file?.id + }); } else { - return await this.ai.sendMessage(this.userId, postData); + return await this.ai.post({ + replyId: this.note.id, + text: text, + fileIds: opts?.file ? [opts?.file.id] : undefined, + cw: opts?.cw, + renoteId: opts?.renote + }); } } diff --git a/src/module.ts b/src/module.ts index 27bcf65..bbdd661 100644 --- a/src/module.ts +++ b/src/module.ts @@ -32,12 +32,13 @@ export default abstract class Module { /** * コンテキストを生成し、ユーザーからの返信を待ち受けます * @param key コンテキストを識別するためのキー - * @param id トークメッセージ上のコンテキストならばトーク相手のID、そうでないなら待ち受ける投稿のID + * @param isChat チャット上のコンテキストかどうか + * @param id チャット上のコンテキストならばチャット相手のID、そうでないなら待ち受ける投稿のID * @param data コンテキストに保存するオプションのデータ */ @bindThis - protected subscribeReply(key: string | null, id: string, data?: any) { - this.ai.subscribeReply(this, key, id, data); + protected subscribeReply(key: string | null, isChat: boolean, id: string, data?: any) { + this.ai.subscribeReply(this, key, isChat, id, data); } /** diff --git a/src/modules/core/index.ts b/src/modules/core/index.ts index 45b8f5f..e961717 100644 --- a/src/modules/core/index.ts +++ b/src/modules/core/index.ts @@ -85,7 +85,7 @@ export default class extends Module { msg.reply(serifs.core.setNameOk(name)); } else { msg.reply(serifs.core.san).then(reply => { - this.subscribeReply(msg.userId, reply.id, { + this.subscribeReply(msg.userId, msg.isChat, msg.isChat ? msg.userId : reply.id, { name: name }); }); @@ -143,7 +143,7 @@ export default class extends Module { done(); } else { msg.reply(serifs.core.yesOrNo).then(reply => { - this.subscribeReply(msg.userId, reply.id, data); + this.subscribeReply(msg.userId, msg.isChat, reply.id, data); }); } } diff --git a/src/modules/guessing-game/index.ts b/src/modules/guessing-game/index.ts index 93ad457..14f4c94 100644 --- a/src/modules/guessing-game/index.ts +++ b/src/modules/guessing-game/index.ts @@ -49,7 +49,7 @@ export default class extends Module { }); msg.reply(serifs.guessingGame.started).then(reply => { - this.subscribeReply(msg.userId, reply.id); + this.subscribeReply(msg.userId, msg.isChat, msg.isChat ? msg.userId : reply.id); }); return true; @@ -83,7 +83,7 @@ export default class extends Module { if (guess == null) { msg.reply(serifs.guessingGame.nan).then(reply => { - this.subscribeReply(msg.userId, reply.id); + this.subscribeReply(msg.userId, msg.isChat, reply.id); }); return; } @@ -121,7 +121,7 @@ export default class extends Module { msg.reply(text).then(reply => { if (!end) { - this.subscribeReply(msg.userId, reply.id); + this.subscribeReply(msg.userId, msg.isChat, reply.id); } }); } diff --git a/src/modules/kazutori/index.ts b/src/modules/kazutori/index.ts index 0df52a2..7e4776b 100644 --- a/src/modules/kazutori/index.ts +++ b/src/modules/kazutori/index.ts @@ -75,7 +75,7 @@ export default class extends Module { postId: post.id }); - this.subscribeReply(null, post.id); + this.subscribeReply(null, false, post.id); this.log('New kazutori game started'); diff --git a/src/modules/reminder/index.ts b/src/modules/reminder/index.ts index 0842edc..1b55330 100644 --- a/src/modules/reminder/index.ts +++ b/src/modules/reminder/index.ts @@ -14,6 +14,7 @@ export default class extends Module { private reminds: loki.Collection<{ userId: string; id: string; + isChat: boolean; thing: string | null; quoteId: string | null; times: number; // 催促した回数(使うのか?) @@ -69,6 +70,7 @@ export default class extends Module { const remind = this.reminds.insertOne({ id: msg.id, userId: msg.userId, + isChat: msg.isChat, thing: thing === '' ? null : thing, quoteId: msg.quoteId, times: 0, @@ -76,13 +78,13 @@ export default class extends Module { }); // メンションをsubscribe - this.subscribeReply(remind!.id, msg.id, { + this.subscribeReply(remind!.id, msg.isChat, msg.isChat ? msg.userId : msg.id, { id: remind!.id }); if (msg.quoteId) { // 引用元をsubscribe - this.subscribeReply(remind!.id, msg.quoteId, { + this.subscribeReply(remind!.id, false, msg.quoteId, { id: remind!.id }); } @@ -124,6 +126,7 @@ export default class extends Module { msg.reply(serifs.reminder.doneFromInvalidUser); return; } else { + if (msg.isChat) this.unsubscribeReply(key); return false; } } @@ -142,22 +145,28 @@ export default class extends Module { if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応 let reply; - 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) + if (remind.isChat) { + this.ai.sendMessage(friend.userId, { + text: serifs.reminder.notifyWithThing(remind.thing, friend.name) }); - } catch (err) { - // renote対象が消されていたらリマインダー解除 - if (err.statusCode === 400) { - this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id); - this.reminds.remove(remind); + } 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) { + this.unsubscribeReply(remind.thing == null && remind.quoteId ? remind.quoteId : remind.id); + this.reminds.remove(remind); + return; + } return; } - return; } - this.subscribeReply(remind.id, reply.id, { + this.subscribeReply(remind.id, remind.isChat, remind.isChat ? remind.userId : reply.id, { id: remind.id }); diff --git a/src/modules/timer/index.ts b/src/modules/timer/index.ts index 1f9d070..4730a0c 100644 --- a/src/modules/timer/index.ts +++ b/src/modules/timer/index.ts @@ -47,6 +47,7 @@ export default class extends Module { // タイマーセット this.setTimeoutWithPersistence(time, { + isChat: msg.isChat, msgId: msg.id, userId: msg.friend.userId, time: str @@ -60,9 +61,15 @@ export default class extends Module { const friend = this.ai.lookupFriend(data.userId); if (friend == null) return; // 処理の流れ上、実際にnullになることは無さそうだけど一応 const text = serifs.timer.notify(data.time, friend.name); - this.ai.post({ - replyId: data.msgId, - text: text - }); + if (data.isChat) { + this.ai.sendMessage(friend.userId, { + text: text + }); + } else { + this.ai.post({ + replyId: data.msgId, + text: text + }); + } } }