import { publishMainStream, publishGroupMessagingStream, } from "@/services/stream.js"; import { publishMessagingStream } from "@/services/stream.js"; import { publishMessagingIndexStream } from "@/services/stream.js"; import { pushNotification } from "@/services/push-notification.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import { MessagingMessages, UserGroupJoinings, Users } from "@/models/index.js"; import { In } from "typeorm"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { UserGroup } from "@/models/entities/user-group.js"; import { toArray } from "@/prelude/array.js"; import { renderReadActivity } from "@/remote/activitypub/renderer/read.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { deliver } from "@/queue/index.js"; import orderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; /** * Mark messages as read */ export async function readUserMessagingMessage( userId: User["id"], otherpartyId: User["id"], messageIds: MessagingMessage["id"][], ) { if (messageIds.length === 0) return; const messages = await MessagingMessages.findBy({ id: In(messageIds), }); for (const message of messages) { if (message.recipientId !== userId) { throw new IdentifiableError( "e140a4bf-49ce-4fb6-b67c-b78dadf6b52f", "Access denied (user).", ); } } // Update documents await MessagingMessages.update( { id: In(messageIds), userId: otherpartyId, recipientId: userId, isRead: false, }, { isRead: true, }, ); // Publish event publishMessagingStream(otherpartyId, userId, "read", messageIds); publishMessagingIndexStream(userId, "read", messageIds); if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, "readAllMessagingMessages"); pushNotification(userId, "readAllMessagingMessages", undefined); } else { // そのユーザーとのメッセージで未読がなければイベント発行 const count = await MessagingMessages.count({ where: { userId: otherpartyId, recipientId: userId, isRead: false, }, take: 1, }); if (!count) { pushNotification(userId, "readAllMessagingMessagesOfARoom", { userId: otherpartyId, }); } } } /** * Mark messages as read */ export async function readGroupMessagingMessage( userId: User["id"], groupId: UserGroup["id"], messageIds: MessagingMessage["id"][], ) { if (messageIds.length === 0) return; // check joined const joining = await UserGroupJoinings.findOneBy({ userId: userId, userGroupId: groupId, }); if (joining == null) { throw new IdentifiableError( "930a270c-714a-46b2-b776-ad27276dc569", "Access denied (group).", ); } const messages = await MessagingMessages.findBy({ id: In(messageIds), }); const reads: MessagingMessage["id"][] = []; for (const message of messages) { if (message.userId === userId) continue; if (message.reads.includes(userId)) continue; // Update document await MessagingMessages.createQueryBuilder() .update() .set({ reads: (() => `array_append("reads", '${joining.userId}')`) as any, }) .where("id = :id", { id: message.id }) .execute(); reads.push(message.id); } // Publish event publishGroupMessagingStream(groupId, "read", { ids: reads, userId: userId, }); publishMessagingIndexStream(userId, "read", reads); if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, "readAllMessagingMessages"); pushNotification(userId, "readAllMessagingMessages", undefined); } else { // そのグループにおいて未読がなければイベント発行 const unreadExist = await MessagingMessages.createQueryBuilder("message") .where("message.groupId = :groupId", { groupId: groupId }) .andWhere("message.userId != :userId", { userId: userId }) .andWhere("NOT (:userId = ANY(message.reads))", { userId: userId }) .andWhere("message.createdAt > :joinedAt", { joinedAt: joining.createdAt, }) // 自分が加入する前の会話については、未読扱いしない .getOne() .then((x) => x != null); if (!unreadExist) { pushNotification(userId, "readAllMessagingMessagesOfARoom", { groupId }); } } } export async function deliverReadActivity( user: { id: User["id"]; host: null }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[], ) { messages = toArray(messages).filter((x) => x.uri); const contents = messages.map((x) => renderReadActivity(user, x)); if (contents.length > 1) { const collection = orderedCollection( null, contents.length, undefined, undefined, contents, ); deliver(user, renderActivity(collection), recipient.inbox); } else { for (const content of contents) { deliver(user, renderActivity(content), recipient.inbox); } } }