diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-15 13:06:06 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-15 13:06:06 +0900 |
| commit | 8f2049bcd261c3fb10afdc8c15cf4edffe1baa71 (patch) | |
| tree | dc5aa1cefc24d3f6eb36bb1723d7433f8a19e5a2 /packages | |
| parent | Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff) | |
| download | sharkey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.tar.gz sharkey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.tar.bz2 sharkey-8f2049bcd261c3fb10afdc8c15cf4edffe1baa71.zip | |
drop messaging (#9919)
* drop messaging (from backend)
* wip
Diffstat (limited to 'packages')
47 files changed, 18 insertions, 3292 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 6a6d1b864a..0524585ca6 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -22,7 +22,6 @@ import { IdService } from './IdService.js'; import { ImageProcessingService } from './ImageProcessingService.js'; import { InstanceActorService } from './InstanceActorService.js'; import { InternalStorageService } from './InternalStorageService.js'; -import { MessagingService } from './MessagingService.js'; import { MetaService } from './MetaService.js'; import { MfmService } from './MfmService.js'; import { ModerationLogService } from './ModerationLogService.js'; @@ -82,7 +81,6 @@ import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js'; import { HashtagEntityService } from './entities/HashtagEntityService.js'; import { InstanceEntityService } from './entities/InstanceEntityService.js'; -import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js'; import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js'; import { MutingEntityService } from './entities/MutingEntityService.js'; import { NoteEntityService } from './entities/NoteEntityService.js'; @@ -146,7 +144,6 @@ const $IdService: Provider = { provide: 'IdService', useExisting: IdService }; const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService }; const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService }; const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService }; -const $MessagingService: Provider = { provide: 'MessagingService', useExisting: MessagingService }; const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService }; const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService }; const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService }; @@ -207,7 +204,6 @@ const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService }; const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService }; const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService }; -const $MessagingMessageEntityService: Provider = { provide: 'MessagingMessageEntityService', useExisting: MessagingMessageEntityService }; const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService }; const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService }; const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService }; @@ -273,7 +269,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ImageProcessingService, InstanceActorService, InternalStorageService, - MessagingService, MetaService, MfmService, ModerationLogService, @@ -333,7 +328,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting GalleryPostEntityService, HashtagEntityService, InstanceEntityService, - MessagingMessageEntityService, ModerationLogEntityService, MutingEntityService, NoteEntityService, @@ -394,7 +388,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ImageProcessingService, $InstanceActorService, $InternalStorageService, - $MessagingService, $MetaService, $MfmService, $ModerationLogService, @@ -454,7 +447,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $GalleryPostEntityService, $HashtagEntityService, $InstanceEntityService, - $MessagingMessageEntityService, $ModerationLogEntityService, $MutingEntityService, $NoteEntityService, @@ -516,7 +508,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ImageProcessingService, InstanceActorService, InternalStorageService, - MessagingService, MetaService, MfmService, ModerationLogService, @@ -575,7 +566,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting GalleryPostEntityService, HashtagEntityService, InstanceEntityService, - MessagingMessageEntityService, ModerationLogEntityService, MutingEntityService, NoteEntityService, @@ -636,7 +626,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ImageProcessingService, $InstanceActorService, $InternalStorageService, - $MessagingService, $MetaService, $MfmService, $ModerationLogService, @@ -695,7 +684,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $GalleryPostEntityService, $HashtagEntityService, $InstanceEntityService, - $MessagingMessageEntityService, $ModerationLogEntityService, $MutingEntityService, $NoteEntityService, diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 784612149d..f70347c46a 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -11,13 +11,9 @@ import type { AdminStreamTypes, AntennaStreamTypes, BroadcastTypes, - ChannelStreamTypes, DriveStreamTypes, - GroupMessagingStreamTypes, InternalStreamTypes, MainStreamTypes, - MessagingIndexStreamTypes, - MessagingStreamTypes, NoteStreamTypes, UserListStreamTypes, UserStreamTypes, @@ -84,11 +80,6 @@ export class GlobalEventService { } @bindThis - public publishChannelStream<K extends keyof ChannelStreamTypes>(channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void { - this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value); - } - - @bindThis public publishUserListStream<K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void { this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); } @@ -99,21 +90,6 @@ export class GlobalEventService { } @bindThis - public publishMessagingStream<K extends keyof MessagingStreamTypes>(userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void { - this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); - } - - @bindThis - public publishGroupMessagingStream<K extends keyof GroupMessagingStreamTypes>(groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void { - this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value); - } - - @bindThis - public publishMessagingIndexStream<K extends keyof MessagingIndexStreamTypes>(userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void { - this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } - - @bindThis public publishNotesStream(note: Packed<'Note'>): void { this.publish('notesStream', null, note); } diff --git a/packages/backend/src/core/MessagingService.ts b/packages/backend/src/core/MessagingService.ts deleted file mode 100644 index 3a8a25c602..0000000000 --- a/packages/backend/src/core/MessagingService.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In, Not } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { DriveFile } from '@/models/entities/DriveFile.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import type { Note } from '@/models/entities/Note.js'; -import type { User, RemoteUser } from '@/models/entities/User.js'; -import type { UserGroup } from '@/models/entities/UserGroup.js'; -import { QueueService } from '@/core/QueueService.js'; -import { toArray } from '@/misc/prelude/array.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js'; -import { IdService } from '@/core/IdService.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; -import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; -import { PushNotificationService } from '@/core/PushNotificationService.js'; -import { bindThis } from '@/decorators.js'; - -@Injectable() -export class MessagingService { - constructor( - @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - @Inject(DI.mutingsRepository) - private mutingsRepository: MutingsRepository, - - private userEntityService: UserEntityService, - private messagingMessageEntityService: MessagingMessageEntityService, - private idService: IdService, - private globalEventService: GlobalEventService, - private apRendererService: ApRendererService, - private queueService: QueueService, - private pushNotificationService: PushNotificationService, - ) { - } - - @bindThis - public async createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { - const message = { - id: this.idService.genId(), - createdAt: new Date(), - fileId: file ? file.id : null, - recipientId: recipientUser ? recipientUser.id : null, - groupId: recipientGroup ? recipientGroup.id : null, - text: text ? text.trim() : null, - userId: user.id, - isRead: false, - reads: [] as any[], - uri, - } as MessagingMessage; - - await this.messagingMessagesRepository.insert(message); - - const messageObj = await this.messagingMessageEntityService.pack(message); - - if (recipientUser) { - if (this.userEntityService.isLocalUser(user)) { - // 自分のストリーム - this.globalEventService.publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj); - this.globalEventService.publishMessagingIndexStream(message.userId, 'message', messageObj); - this.globalEventService.publishMainStream(message.userId, 'messagingMessage', messageObj); - } - - if (this.userEntityService.isLocalUser(recipientUser)) { - // 相手のストリーム - this.globalEventService.publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj); - this.globalEventService.publishMessagingIndexStream(recipientUser.id, 'message', messageObj); - this.globalEventService.publishMainStream(recipientUser.id, 'messagingMessage', messageObj); - } - } else if (recipientGroup) { - // グループのストリーム - this.globalEventService.publishGroupMessagingStream(recipientGroup.id, 'message', messageObj); - - // メンバーのストリーム - const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id }); - for (const joining of joinings) { - this.globalEventService.publishMessagingIndexStream(joining.userId, 'message', messageObj); - this.globalEventService.publishMainStream(joining.userId, 'messagingMessage', messageObj); - } - } - - // 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する - setTimeout(async () => { - const freshMessage = await this.messagingMessagesRepository.findOneBy({ id: message.id }); - if (freshMessage == null) return; // メッセージが削除されている場合もある - - if (recipientUser && this.userEntityService.isLocalUser(recipientUser)) { - if (freshMessage.isRead) return; // 既読 - - //#region ただしミュートされているなら発行しない - const mute = await this.mutingsRepository.findBy({ - muterId: recipientUser.id, - }); - if (mute.map(m => m.muteeId).includes(user.id)) return; - //#endregion - - this.globalEventService.publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj); - this.pushNotificationService.pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj); - } else if (recipientGroup) { - const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) }); - for (const joining of joinings) { - if (freshMessage.reads.includes(joining.userId)) return; // 既読 - this.globalEventService.publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj); - this.pushNotificationService.pushNotification(joining.userId, 'unreadMessagingMessage', messageObj); - } - } - }, 2000); - - if (recipientUser && this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipientUser)) { - const note = { - id: message.id, - createdAt: message.createdAt, - fileIds: message.fileId ? [message.fileId] : [], - text: message.text, - userId: message.userId, - visibility: 'specified', - mentions: [recipientUser].map(u => u.id), - mentionedRemoteUsers: JSON.stringify([recipientUser].map(u => ({ - uri: u.uri, - username: u.username, - host: u.host, - }))), - } as Note; - - const activity = this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false, true), note)); - - this.queueService.deliver(user, activity, recipientUser.inbox); - } - return messageObj; - } - - @bindThis - public async deleteMessage(message: MessagingMessage) { - await this.messagingMessagesRepository.delete(message.id); - this.postDeleteMessage(message); - } - - @bindThis - private async postDeleteMessage(message: MessagingMessage) { - if (message.recipientId) { - const user = await this.usersRepository.findOneByOrFail({ id: message.userId }); - const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId }); - - if (this.userEntityService.isLocalUser(user)) this.globalEventService.publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); - if (this.userEntityService.isLocalUser(recipient)) this.globalEventService.publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); - - if (this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipient)) { - const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), user)); - this.queueService.deliver(user, activity, recipient.inbox); - } - } else if (message.groupId) { - this.globalEventService.publishGroupMessagingStream(message.groupId, 'deleted', message.id); - } - } - - /** - * Mark messages as read - */ - @bindThis - public async readUserMessagingMessage( - userId: User['id'], - otherpartyId: User['id'], - messageIds: MessagingMessage['id'][], - ) { - if (messageIds.length === 0) return; - - const messages = await this.messagingMessagesRepository.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 this.messagingMessagesRepository.update({ - id: In(messageIds), - userId: otherpartyId, - recipientId: userId, - isRead: false, - }, { - isRead: true, - }); - - // Publish event - this.globalEventService.publishMessagingStream(otherpartyId, userId, 'read', messageIds); - this.globalEventService.publishMessagingIndexStream(userId, 'read', messageIds); - - if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) { - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages'); - this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined); - } else { - // そのユーザーとのメッセージで未読がなければイベント発行 - const count = await this.messagingMessagesRepository.count({ - where: { - userId: otherpartyId, - recipientId: userId, - isRead: false, - }, - take: 1, - }); - - if (!count) { - this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId }); - } - } - } - - /** - * Mark messages as read - */ - @bindThis - public async readGroupMessagingMessage( - userId: User['id'], - groupId: UserGroup['id'], - messageIds: MessagingMessage['id'][], - ) { - if (messageIds.length === 0) return; - - // check joined - const joining = await this.userGroupJoiningsRepository.findOneBy({ - userId: userId, - userGroupId: groupId, - }); - - if (joining == null) { - throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).'); - } - - const messages = await this.messagingMessagesRepository.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 this.messagingMessagesRepository.createQueryBuilder().update() - .set({ - reads: (() => `array_append("reads", '${joining.userId}')`) as any, - }) - .where('id = :id', { id: message.id }) - .execute(); - - reads.push(message.id); - } - - // Publish event - this.globalEventService.publishGroupMessagingStream(groupId, 'read', { - ids: reads, - userId: userId, - }); - this.globalEventService.publishMessagingIndexStream(userId, 'read', reads); - - if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) { - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages'); - this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined); - } else { - // そのグループにおいて未読がなければイベント発行 - const unreadExist = await this.messagingMessagesRepository.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) { - this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId }); - } - } - } - - @bindThis - public async deliverReadActivity(user: { id: User['id']; host: null; }, recipient: RemoteUser, messages: MessagingMessage | MessagingMessage[]) { - messages = toArray(messages).filter(x => x.uri); - const contents = messages.map(x => this.apRendererService.renderRead(user, x)); - - if (contents.length > 1) { - const collection = this.apRendererService.renderOrderedCollection(null, contents.length, undefined, undefined, contents); - this.queueService.deliver(user, this.apRendererService.addContext(collection), recipient.inbox); - } else { - for (const content of contents) { - this.queueService.deliver(user, this.apRendererService.addContext(content), recipient.inbox); - } - } - } -} diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index b18b7bb2cd..aff233a3e4 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -11,15 +11,12 @@ import { bindThis } from '@/decorators.js'; // Defined also packages/sw/types.ts#L13 type pushNotificationsTypes = { 'notification': Packed<'Notification'>; - 'unreadMessagingMessage': Packed<'MessagingMessage'>; 'unreadAntennaNote': { antenna: { id: string, name: string }; note: Packed<'Note'>; }; 'readNotifications': { notificationIds: string[] }; 'readAllNotifications': undefined; - 'readAllMessagingMessages': undefined; - 'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string }; 'readAntenna': { antennaId: string }; 'readAllAntennas': undefined; }; @@ -40,11 +37,10 @@ function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pus reply: undefined, renote: undefined, user: type === 'notification' ? undefined as any : body.note.user, - } + }, } : {}), }; - return body; } @Injectable() @@ -81,8 +77,6 @@ export class PushNotificationService { if ([ 'readNotifications', 'readAllNotifications', - 'readAllMessagingMessages', - 'readAllMessagingMessagesOfARoom', 'readAntenna', 'readAllAntennas', ].includes(type) && !subscription.sendReadMessage) continue; diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 9a894826c8..d0a4ad7a75 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -1,13 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import escapeRegexp from 'escape-regexp'; import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; +import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import { Cache } from '@/misc/cache.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserCacheService } from '@/core/UserCacheService.js'; import type { Note } from '@/models/entities/Note.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import { bindThis } from '@/decorators.js'; import { RemoteUser, User } from '@/models/entities/User.js'; import { getApId } from './type.js'; @@ -42,9 +41,6 @@ export class ApDbResolverService { @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -101,23 +97,6 @@ export class ApDbResolverService { } } - @bindThis - public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> { - const parsed = this.parseUri(value); - - if (parsed.local) { - if (parsed.type !== 'notes') return null; - - return await this.messagingMessagesRepository.findOneBy({ - id: parsed.id, - }); - } else { - return await this.messagingMessagesRepository.findOneBy({ - uri: parsed.uri, - }); - } - } - /** * AP Person => Misskey User in DB */ diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 62f3827343..15d7d097c9 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -19,8 +19,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import type { RemoteUser } from '@/models/entities/User.js'; import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; @@ -51,9 +50,6 @@ export class ApInboxService { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, @@ -81,7 +77,6 @@ export class ApInboxService { private apPersonService: ApPersonService, private apQuestionService: ApQuestionService, private queueService: QueueService, - private messagingService: MessagingService, ) { this.logger = this.apLoggerService.logger; } @@ -124,8 +119,6 @@ export class ApInboxService { await this.delete(actor, activity); } else if (isUpdate(activity)) { await this.update(actor, activity); - } else if (isRead(activity)) { - await this.read(actor, activity); } else if (isFollow(activity)) { await this.follow(actor, activity); } else if (isAccept(activity)) { @@ -186,29 +179,6 @@ export class ApInboxService { } @bindThis - private async read(actor: RemoteUser, activity: IRead): Promise<string> { - const id = await getApId(activity.object); - - if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) { - return `skip: Read to foreign host (${id})`; - } - - const messageId = id.split('/').pop(); - - const message = await this.messagingMessagesRepository.findOneBy({ id: messageId }); - if (message == null) { - return 'skip: message not found'; - } - - if (actor.id !== message.recipientId) { - return 'skip: actor is not a message recipient'; - } - - await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); - return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; - } - - @bindThis private async accept(actor: RemoteUser, activity: IAccept): Promise<string> { const uri = activity.id ?? activity; @@ -504,16 +474,7 @@ export class ApInboxService { const note = await this.apDbResolverService.getNoteFromApId(uri); if (note == null) { - const message = await this.apDbResolverService.getMessageFromApId(uri); - if (message == null) return 'message not found'; - - if (message.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; - } - - await this.messagingService.deleteMessage(message); - - return 'ok: message deleted'; + return 'message not found'; } if (note.userId !== actor.id) { diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 92bc1869e5..c4f9cc0556 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -13,7 +13,6 @@ import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import type { Poll } from '@/models/entities/Poll.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import type { PollVote } from '@/models/entities/PollVote.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { MfmService } from '@/core/MfmService.js'; @@ -293,7 +292,7 @@ export class ApRendererService { } @bindThis - public async renderNote(note: Note, dive = true, isTalk = false): Promise<IPost> { + public async renderNote(note: Note, dive = true): Promise<IPost> { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); @@ -407,11 +406,7 @@ export class ApRendererService { }, })), } as const : {}; - - const asTalk = isTalk ? { - _misskey_talk: true, - } as const : {}; - + return { id: `${this.config.url}/notes/${note.id}`, type: 'Note', @@ -433,7 +428,6 @@ export class ApRendererService { sensitive: note.cw != null || files.some(file => file.isSensitive), tag, ...asPoll, - ...asTalk, }; } @@ -533,15 +527,6 @@ export class ApRendererService { } @bindThis - public renderRead(user: { id: User['id'] }, message: MessagingMessage): IRead { - return { - type: 'Read', - actor: `${this.config.url}/users/${user.id}`, - object: message.uri!, - }; - } - - @bindThis public renderReject(object: any, user: { id: User['id'] }): IReject { return { type: 'Reject', @@ -643,7 +628,6 @@ export class ApRendererService { '_misskey_quote': 'misskey:_misskey_quote', '_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_votes': 'misskey:_misskey_votes', - '_misskey_talk': 'misskey:_misskey_talk', 'isCat': 'misskey:isCat', // vcard vcard: 'http://www.w3.org/2006/vcard/ns#', diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index d4c4836887..eced94a926 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -1,7 +1,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; +import type { PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { RemoteUser } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; @@ -16,7 +16,6 @@ import { IdService } from '@/core/IdService.js'; import { PollService } from '@/core/PollService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { MessagingService } from '@/core/MessagingService.js'; import { bindThis } from '@/decorators.js'; import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports @@ -47,9 +46,6 @@ export class ApNoteService { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - private idService: IdService, private apMfmService: ApMfmService, private apResolverService: ApResolverService, @@ -64,7 +60,6 @@ export class ApNoteService { private apImageService: ApImageService, private apQuestionService: ApQuestionService, private metaService: MetaService, - private messagingService: MessagingService, private appLockService: AppLockService, private pollService: PollService, private noteCreateService: NoteCreateService, @@ -165,8 +160,6 @@ export class ApNoteService { } } - let isMessaging = note._misskey_talk && visibility === 'specified'; - const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); const apHashtags = await extractApHashtags(note.tag); @@ -193,17 +186,6 @@ export class ApNoteService { return x; } }).catch(async err => { - // トークだったらinReplyToのエラーは無視 - const uri = getApId(note.inReplyTo); - if (uri.startsWith(this.config.url + '/')) { - const id = uri.split('/').pop(); - const talk = await this.messagingMessagesRepository.findOneBy({ id }); - if (talk) { - isMessaging = true; - return null; - } - } - this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); throw err; }) @@ -292,14 +274,7 @@ export class ApNoteService { const apEmojis = emojis.map(emoji => emoji.name); const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); - - if (isMessaging) { - for (const recipient of visibleUsers) { - await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id); - return null; - } - } - + return await this.noteCreateService.create(actor, { createdAt: note.published ? new Date(note.published) : null, files, diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 9dc7ed4e31..268bf99119 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -113,7 +113,6 @@ export interface IPost extends IObject { _misskey_quote?: string; _misskey_content?: string; quoteUrl?: string; - _misskey_talk?: boolean; } export interface IQuestion extends IObject { diff --git a/packages/backend/src/core/entities/MessagingMessageEntityService.ts b/packages/backend/src/core/entities/MessagingMessageEntityService.ts deleted file mode 100644 index cdb752dd81..0000000000 --- a/packages/backend/src/core/entities/MessagingMessageEntityService.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository } from '@/models/index.js'; -import { awaitAll } from '@/misc/prelude/await-all.js'; -import type { Packed } from '@/misc/schema.js'; -import type { } from '@/models/entities/Blocking.js'; -import type { User } from '@/models/entities/User.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import { UserEntityService } from './UserEntityService.js'; -import { DriveFileEntityService } from './DriveFileEntityService.js'; -import { UserGroupEntityService } from './UserGroupEntityService.js'; -import { bindThis } from '@/decorators.js'; - -@Injectable() -export class MessagingMessageEntityService { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private userEntityService: UserEntityService, - private userGroupEntityService: UserGroupEntityService, - private driveFileEntityService: DriveFileEntityService, - ) { - } - - @bindThis - public async pack( - src: MessagingMessage['id'] | MessagingMessage, - me?: { id: User['id'] } | null | undefined, - options?: { - populateRecipient?: boolean, - populateGroup?: boolean, - }, - ): Promise<Packed<'MessagingMessage'>> { - const opts = options ?? { - populateRecipient: true, - populateGroup: true, - }; - - const message = typeof src === 'object' ? src : await this.messagingMessagesRepository.findOneByOrFail({ id: src }); - - return { - id: message.id, - createdAt: message.createdAt.toISOString(), - text: message.text, - userId: message.userId, - user: await this.userEntityService.pack(message.user ?? message.userId, me), - recipientId: message.recipientId, - recipient: message.recipientId && opts.populateRecipient ? await this.userEntityService.pack(message.recipient ?? message.recipientId, me) : undefined, - groupId: message.groupId, - group: message.groupId && opts.populateGroup ? await this.userGroupEntityService.pack(message.group ?? message.groupId) : undefined, - fileId: message.fileId, - file: message.fileId ? await this.driveFileEntityService.pack(message.fileId) : null, - isRead: message.isRead, - reads: message.reads, - }; - } -} - diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index fa3337c019..19bae443c8 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -12,7 +12,7 @@ import { Cache } from '@/misc/cache.js'; import type { Instance } from '@/models/entities/Instance.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; -import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -102,9 +102,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.announcementReadsRepository) private announcementReadsRepository: AnnouncementReadsRepository, - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - @Inject(DI.userGroupJoiningsRepository) private userGroupJoiningsRepository: UserGroupJoiningsRepository, @@ -205,36 +202,6 @@ export class UserEntityService implements OnModuleInit { } @bindThis - public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> { - const mute = await this.mutingsRepository.findBy({ - muterId: userId, - }); - - const joinings = await this.userGroupJoiningsRepository.findBy({ userId: userId }); - - const groupQs = Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder('message') - .where('message.groupId = :groupId', { groupId: j.userGroupId }) - .andWhere('message.userId != :userId', { userId: userId }) - .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) - .andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない - .getOne().then(x => x != null))); - - const [withUser, withGroups] = await Promise.all([ - this.messagingMessagesRepository.count({ - where: { - recipientId: userId, - isRead: false, - ...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}), - }, - take: 1, - }).then(count => count > 0), - groupQs, - ]); - - return withUser || withGroups.some(x => x); - } - - @bindThis public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> { const reads = await this.announcementReadsRepository.findBy({ userId: userId, @@ -492,7 +459,6 @@ export class UserEntityService implements OnModuleInit { hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id), hasUnreadAntenna: this.getHasUnreadAntenna(user.id), hasUnreadChannel: this.getHasUnreadChannel(user.id), - hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id), hasUnreadNotification: this.getHasUnreadNotification(user.id), hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id), mutedWords: profile!.mutedWords, diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 3fb0cd4dae..da21409541 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -47,7 +47,6 @@ export const DI = { authSessionsRepository: Symbol('authSessionsRepository'), accessTokensRepository: Symbol('accessTokensRepository'), signinsRepository: Symbol('signinsRepository'), - messagingMessagesRepository: Symbol('messagingMessagesRepository'), pagesRepository: Symbol('pagesRepository'), pageLikesRepository: Symbol('pageLikesRepository'), galleryPostsRepository: Symbol('galleryPostsRepository'), diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 7aeb65f296..b3db19b338 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -10,7 +10,6 @@ import { import { packedNoteSchema } from '@/models/schema/note.js'; import { packedUserListSchema } from '@/models/schema/user-list.js'; import { packedAppSchema } from '@/models/schema/app.js'; -import { packedMessagingMessageSchema } from '@/models/schema/messaging-message.js'; import { packedNotificationSchema } from '@/models/schema/notification.js'; import { packedDriveFileSchema } from '@/models/schema/drive-file.js'; import { packedDriveFolderSchema } from '@/models/schema/drive-folder.js'; @@ -42,7 +41,6 @@ export const refs = { UserList: packedUserListSchema, UserGroup: packedUserGroupSchema, App: packedAppSchema, - MessagingMessage: packedMessagingMessageSchema, Note: packedNoteSchema, NoteReaction: packedNoteReactionSchema, NoteFavorite: packedNoteFavoriteSchema, diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 2a235bc6fc..231dbb225b 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -256,12 +256,6 @@ const $signinsRepository: Provider = { inject: [DI.db], }; -const $messagingMessagesRepository: Provider = { - provide: DI.messagingMessagesRepository, - useFactory: (db: DataSource) => db.getRepository(MessagingMessage), - inject: [DI.db], -}; - const $pagesRepository: Provider = { provide: DI.pagesRepository, useFactory: (db: DataSource) => db.getRepository(Page), @@ -458,7 +452,6 @@ const $roleAssignmentsRepository: Provider = { $authSessionsRepository, $accessTokensRepository, $signinsRepository, - $messagingMessagesRepository, $pagesRepository, $pageLikesRepository, $galleryPostsRepository, @@ -528,7 +521,6 @@ const $roleAssignmentsRepository: Provider = { $authSessionsRepository, $accessTokensRepository, $signinsRepository, - $messagingMessagesRepository, $pagesRepository, $pageLikesRepository, $galleryPostsRepository, diff --git a/packages/backend/src/models/entities/MessagingMessage.ts b/packages/backend/src/models/entities/MessagingMessage.ts deleted file mode 100644 index 69fc9815d4..0000000000 --- a/packages/backend/src/models/entities/MessagingMessage.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './User.js'; -import { DriveFile } from './DriveFile.js'; -import { UserGroup } from './UserGroup.js'; - -@Entity() -export class MessagingMessage { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the MessagingMessage.', - }) - public createdAt: Date; - - @Index() - @Column({ - ...id(), - comment: 'The sender user ID.', - }) - public userId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: User | null; - - @Index() - @Column({ - ...id(), nullable: true, - comment: 'The recipient user ID.', - }) - public recipientId: User['id'] | null; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public recipient: User | null; - - @Index() - @Column({ - ...id(), nullable: true, - comment: 'The recipient group ID.', - }) - public groupId: UserGroup['id'] | null; - - @ManyToOne(type => UserGroup, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public group: UserGroup | null; - - @Column('varchar', { - length: 4096, nullable: true, - }) - public text: string | null; - - @Column('boolean', { - default: false, - }) - public isRead: boolean; - - @Column('varchar', { - length: 512, nullable: true, - }) - public uri: string | null; - - @Column({ - ...id(), - array: true, default: '{}', - }) - public reads: User['id'][]; - - @Column({ - ...id(), - nullable: true, - }) - public fileId: DriveFile['id'] | null; - - @ManyToOne(type => DriveFile, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public file: DriveFile | null; -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 50697597ad..1494905277 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -22,7 +22,6 @@ import { GalleryLike } from '@/models/entities/GalleryLike.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import { Hashtag } from '@/models/entities/Hashtag.js'; import { Instance } from '@/models/entities/Instance.js'; -import { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import { Meta } from '@/models/entities/Meta.js'; import { ModerationLog } from '@/models/entities/ModerationLog.js'; import { MutedNote } from '@/models/entities/MutedNote.js'; @@ -93,7 +92,6 @@ export { GalleryPost, Hashtag, Instance, - MessagingMessage, Meta, ModerationLog, MutedNote, @@ -163,7 +161,6 @@ export type GalleryLikesRepository = Repository<GalleryLike>; export type GalleryPostsRepository = Repository<GalleryPost>; export type HashtagsRepository = Repository<Hashtag>; export type InstancesRepository = Repository<Instance>; -export type MessagingMessagesRepository = Repository<MessagingMessage>; export type MetasRepository = Repository<Meta>; export type ModerationLogsRepository = Repository<ModerationLog>; export type MutedNotesRepository = Repository<MutedNote>; diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/backend/src/models/schema/messaging-message.ts deleted file mode 100644 index b1ffa45955..0000000000 --- a/packages/backend/src/models/schema/messaging-message.ts +++ /dev/null @@ -1,73 +0,0 @@ -export const packedMessagingMessageSchema = { - type: 'object', - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', - }, - userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - user: { - type: 'object', - ref: 'UserLite', - optional: true, nullable: false, - }, - text: { - type: 'string', - optional: false, nullable: true, - }, - fileId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - }, - file: { - type: 'object', - optional: true, nullable: true, - ref: 'DriveFile', - }, - recipientId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - }, - recipient: { - type: 'object', - optional: true, nullable: true, - ref: 'UserLite', - }, - groupId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - }, - group: { - type: 'object', - optional: true, nullable: true, - ref: 'UserGroup', - }, - isRead: { - type: 'boolean', - optional: true, nullable: false, - }, - reads: { - type: 'array', - optional: true, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - }, - }, -} as const; diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts index 1fc9352539..c390018b46 100644 --- a/packages/backend/src/models/schema/user.ts +++ b/packages/backend/src/models/schema/user.ts @@ -311,10 +311,6 @@ export const packedMeDetailedOnlySchema = { type: 'boolean', nullable: false, optional: false, }, - hasUnreadMessagingMessage: { - type: 'boolean', - nullable: false, optional: false, - }, hasUnreadNotification: { type: 'boolean', nullable: false, optional: false, diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 33b924e776..8cf259eb16 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -30,7 +30,6 @@ import { GalleryLike } from '@/models/entities/GalleryLike.js'; import { GalleryPost } from '@/models/entities/GalleryPost.js'; import { Hashtag } from '@/models/entities/Hashtag.js'; import { Instance } from '@/models/entities/Instance.js'; -import { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import { Meta } from '@/models/entities/Meta.js'; import { ModerationLog } from '@/models/entities/ModerationLog.js'; import { MutedNote } from '@/models/entities/MutedNote.js'; @@ -167,7 +166,6 @@ export const entities = [ SwSubscription, AbuseUserReport, RegistrationTicket, - MessagingMessage, Signin, ModerationLog, Clip, diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index b605f3c8ab..a5a5f9e7f9 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -30,8 +30,6 @@ import { HashtagChannelService } from './api/stream/channels/hashtag.js'; import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js'; import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js'; import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js'; -import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js'; -import { MessagingChannelService } from './api/stream/channels/messaging.js'; import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js'; import { ServerStatsChannelService } from './api/stream/channels/server-stats.js'; import { UserListChannelService } from './api/stream/channels/user-list.js'; @@ -71,8 +69,6 @@ import { UserListChannelService } from './api/stream/channels/user-list.js'; HomeTimelineChannelService, HybridTimelineChannelService, LocalTimelineChannelService, - MessagingIndexChannelService, - MessagingChannelService, QueueStatsChannelService, ServerStatsChannelService, UserListChannelService, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 4a55c6cbe3..933dcbebe6 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -195,7 +195,6 @@ import * as ep___i_notifications from './endpoints/i/notifications.js'; import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; import * as ep___i_pages from './endpoints/i/pages.js'; import * as ep___i_pin from './endpoints/i/pin.js'; -import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js'; import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; @@ -218,11 +217,6 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; -import * as ep___messaging_history from './endpoints/messaging/history.js'; -import * as ep___messaging_messages from './endpoints/messaging/messages.js'; -import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js'; -import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; -import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; import * as ep___meta from './endpoints/meta.js'; import * as ep___emojis from './endpoints/emojis.js'; import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; @@ -530,7 +524,6 @@ const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default }; const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default }; const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default }; -const $i_readAllMessagingMessages: Provider = { provide: 'ep:i/read-all-messaging-messages', useClass: ep___i_readAllMessagingMessages.default }; const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default }; const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default }; const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default }; @@ -553,11 +546,6 @@ const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default }; const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default }; -const $messaging_history: Provider = { provide: 'ep:messaging/history', useClass: ep___messaging_history.default }; -const $messaging_messages: Provider = { provide: 'ep:messaging/messages', useClass: ep___messaging_messages.default }; -const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/create', useClass: ep___messaging_messages_create.default }; -const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default }; -const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default }; const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default }; const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; @@ -869,7 +857,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_pageLikes, $i_pages, $i_pin, - $i_readAllMessagingMessages, $i_readAllUnreadNotes, $i_readAnnouncement, $i_regenerateToken, @@ -892,11 +879,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, - $messaging_history, - $messaging_messages, - $messaging_messages_create, - $messaging_messages_delete, - $messaging_messages_read, $meta, $emojis, $miauth_genToken, @@ -1202,7 +1184,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_pageLikes, $i_pages, $i_pin, - $i_readAllMessagingMessages, $i_readAllUnreadNotes, $i_readAnnouncement, $i_regenerateToken, @@ -1225,11 +1206,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, - $messaging_history, - $messaging_messages, - $messaging_messages_create, - $messaging_messages_delete, - $messaging_messages_read, $meta, $emojis, $miauth_genToken, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 55e1900d51..639cf30195 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -194,7 +194,6 @@ import * as ep___i_notifications from './endpoints/i/notifications.js'; import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; import * as ep___i_pages from './endpoints/i/pages.js'; import * as ep___i_pin from './endpoints/i/pin.js'; -import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js'; import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; @@ -217,11 +216,6 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; -import * as ep___messaging_history from './endpoints/messaging/history.js'; -import * as ep___messaging_messages from './endpoints/messaging/messages.js'; -import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js'; -import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; -import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; import * as ep___meta from './endpoints/meta.js'; import * as ep___emojis from './endpoints/emojis.js'; import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; @@ -527,7 +521,6 @@ const eps = [ ['i/page-likes', ep___i_pageLikes], ['i/pages', ep___i_pages], ['i/pin', ep___i_pin], - ['i/read-all-messaging-messages', ep___i_readAllMessagingMessages], ['i/read-all-unread-notes', ep___i_readAllUnreadNotes], ['i/read-announcement', ep___i_readAnnouncement], ['i/regenerate-token', ep___i_regenerateToken], @@ -550,11 +543,6 @@ const eps = [ ['i/webhooks/show', ep___i_webhooks_show], ['i/webhooks/update', ep___i_webhooks_update], ['i/webhooks/delete', ep___i_webhooks_delete], - ['messaging/history', ep___messaging_history], - ['messaging/messages', ep___messaging_messages], - ['messaging/messages/create', ep___messaging_messages_create], - ['messaging/messages/delete', ep___messaging_messages_delete], - ['messaging/messages/read', ep___messaging_messages_read], ['meta', ep___meta], ['emojis', ep___emojis], ['miauth/gen-token', ep___miauth_genToken], diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts deleted file mode 100644 index 109d6d1068..0000000000 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { DI } from '@/di-symbols.js'; - -export const meta = { - tags: ['account', 'messaging'], - - requireCredential: true, - - kind: 'write:account', -} as const; - -export const paramDef = { - type: 'object', - properties: {}, - required: [], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - private globalEventService: GlobalEventService, - ) { - super(meta, paramDef, async (ps, me) => { - // Update documents - await this.messagingMessagesRepository.update({ - recipientId: me.id, - isRead: false, - }, { - isRead: true, - }); - - const joinings = await this.userGroupJoiningsRepository.findBy({ userId: me.id }); - - await Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder().update() - .set({ - reads: (() => `array_append("reads", '${me.id}')`) as any, - }) - .where('groupId = :groupId', { groupId: j.userGroupId }) - .andWhere('userId != :userId', { userId: me.id }) - .andWhere('NOT (:userId = ANY(reads))', { userId: me.id }) - .execute())); - - this.globalEventService.publishMainStream(me.id, 'readAllMessagingMessages'); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts deleted file mode 100644 index 0b6099d4ac..0000000000 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Brackets } from 'typeorm'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; -import type { MutingsRepository, UserGroupJoiningsRepository, MessagingMessagesRepository } from '@/models/index.js'; -import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; -import { DI } from '@/di-symbols.js'; - -export const meta = { - tags: ['messaging'], - - requireCredential: true, - - kind: 'read:messaging', - - res: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - group: { type: 'boolean', default: false }, - }, - required: [], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.mutingsRepository) - private mutingsRepository: MutingsRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - private messagingMessageEntityService: MessagingMessageEntityService, - ) { - super(meta, paramDef, async (ps, me) => { - const mute = await this.mutingsRepository.findBy({ - muterId: me.id, - }); - - const groups = ps.group ? await this.userGroupJoiningsRepository.findBy({ - userId: me.id, - }).then(xs => xs.map(x => x.userGroupId)) : []; - - if (ps.group && groups.length === 0) { - return []; - } - - const history: MessagingMessage[] = []; - - for (let i = 0; i < ps.limit; i++) { - const found = ps.group - ? history.map(m => m.groupId!) - : history.map(m => (m.userId === me.id) ? m.recipientId! : m.userId!); - - const query = this.messagingMessagesRepository.createQueryBuilder('message') - .orderBy('message.createdAt', 'DESC'); - - if (ps.group) { - query.where('message.groupId IN (:...groups)', { groups: groups }); - - if (found.length > 0) { - query.andWhere('message.groupId NOT IN (:...found)', { found: found }); - } - } else { - query.where(new Brackets(qb => { qb - .where('message.userId = :userId', { userId: me.id }) - .orWhere('message.recipientId = :userId', { userId: me.id }); - })); - query.andWhere('message.groupId IS NULL'); - - if (found.length > 0) { - query.andWhere('message.userId NOT IN (:...found)', { found: found }); - query.andWhere('message.recipientId NOT IN (:...found)', { found: found }); - } - - if (mute.length > 0) { - query.andWhere('message.userId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); - query.andWhere('message.recipientId NOT IN (:...mute)', { mute: mute.map(m => m.muteeId) }); - } - } - - const message = await query.getOne(); - - if (message) { - history.push(message); - } else { - break; - } - } - - return await Promise.all(history.map(h => this.messagingMessageEntityService.pack(h.id, me))); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts deleted file mode 100644 index 3673e252ae..0000000000 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Brackets } from 'typeorm'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UsersRepository, UserGroupsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js'; -import { QueryService } from '@/core/QueryService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../error.js'; -import { GetterService } from '@/server/api/GetterService.js'; - -export const meta = { - tags: ['messaging'], - - requireCredential: true, - - kind: 'read:messaging', - - res: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '11795c64-40ea-4198-b06e-3c873ed9039d', - }, - - noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f', - }, - - groupAccessDenied: { - message: 'You can not read messages of groups that you have not joined.', - code: 'GROUP_ACCESS_DENIED', - id: 'a053a8dd-a491-4718-8f87-50775aad9284', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - markAsRead: { type: 'boolean', default: true }, - }, - anyOf: [ - { - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], - }, - { - properties: { - groupId: { type: 'string', format: 'misskey:id' }, - }, - required: ['groupId'], - }, - ], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - @Inject(DI.userGroupsRepository) - private userGroupRepository: UserGroupsRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - private messagingMessageEntityService: MessagingMessageEntityService, - private messagingService: MessagingService, - private userEntityService: UserEntityService, - private queryService: QueryService, - private getterService: GetterService, - ) { - super(meta, paramDef, async (ps, me) => { - if (ps.userId != null) { - // Fetch recipient (user) - const recipient = await this.getterService.getUser(ps.userId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw err; - }); - - const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(new Brackets(qb => { qb - .where('message.userId = :meId') - .andWhere('message.recipientId = :recipientId'); - })) - .orWhere(new Brackets(qb => { qb - .where('message.userId = :recipientId') - .andWhere('message.recipientId = :meId'); - })); - })) - .setParameter('meId', me.id) - .setParameter('recipientId', recipient.id); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - this.messagingService.readUserMessagingMessage(me.id, recipient.id, messages.filter(m => m.recipientId === me.id).map(x => x.id)); - - // リモートユーザーとのメッセージだったら既読配信 - if (this.userEntityService.isLocalUser(me) && this.userEntityService.isRemoteUser(recipient)) { - this.messagingService.deliverReadActivity(me, recipient, messages); - } - } - - return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { - populateRecipient: false, - }))); - } else if (ps.groupId != null) { - // Fetch recipient (group) - const recipientGroup = await this.userGroupRepository.findOneBy({ id: ps.groupId }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await this.userGroupJoiningsRepository.findOneBy({ - userId: me.id, - userGroupId: recipientGroup.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } - - const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere('message.groupId = :groupId', { groupId: recipientGroup.id }); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id)); - } - - return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, { - populateGroup: false, - }))); - } - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts deleted file mode 100644 index e9ffc7a9eb..0000000000 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import ms from 'ms'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js'; -import type { User } from '@/models/entities/User.js'; -import type { UserGroup } from '@/models/entities/UserGroup.js'; -import { GetterService } from '@/server/api/GetterService.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['messaging'], - - requireCredential: true, - - kind: 'write:messaging', - - limit: { - duration: ms('1hour'), - max: 120, - }, - - res: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', - }, - - errors: { - recipientIsYourself: { - message: 'You can not send a message to yourself.', - code: 'RECIPIENT_IS_YOURSELF', - id: '17e2ba79-e22a-4cbc-bf91-d327643f4a7e', - }, - - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '11795c64-40ea-4198-b06e-3c873ed9039d', - }, - - noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537', - }, - - groupAccessDenied: { - message: 'You can not send messages to groups that you have not joined.', - code: 'GROUP_ACCESS_DENIED', - id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd', - }, - - noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: '4372b8e2-185d-4146-8749-2f68864a3e5f', - }, - - contentRequired: { - message: 'Content required. You need to set text or fileId.', - code: 'CONTENT_REQUIRED', - id: '25587321-b0e6-449c-9239-f8925092942c', - }, - - youHaveBeenBlocked: { - message: 'You cannot send a message because you have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: 'c15a5199-7422-4968-941a-2a462c478f7d', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - text: { type: 'string', nullable: true, maxLength: 3000 }, - fileId: { type: 'string', format: 'misskey:id' }, - }, - anyOf: [ - { - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], - }, - { - properties: { - groupId: { type: 'string', format: 'misskey:id' }, - }, - required: ['groupId'], - }, - ], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.userGroupsRepository) - private userGroupsRepository: UserGroupsRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - private getterService: GetterService, - private messagingService: MessagingService, - ) { - super(meta, paramDef, async (ps, me) => { - let recipientUser: User | null; - let recipientGroup: UserGroup | null; - - if (ps.userId != null) { - // Myself - if (ps.userId === me.id) { - throw new ApiError(meta.errors.recipientIsYourself); - } - - // Fetch recipient (user) - recipientUser = await this.getterService.getUser(ps.userId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw err; - }); - - // Check blocking - const block = await this.blockingsRepository.findOneBy({ - blockerId: recipientUser.id, - blockeeId: me.id, - }); - if (block) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } else if (ps.groupId != null) { - // Fetch recipient (group) - recipientGroup = await this.userGroupsRepository.findOneBy({ id: ps.groupId! }); - - if (recipientGroup == null) { - throw new ApiError(meta.errors.noSuchGroup); - } - - // check joined - const joining = await this.userGroupJoiningsRepository.findOneBy({ - userId: me.id, - userGroupId: recipientGroup.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.groupAccessDenied); - } - } - - let file = null; - if (ps.fileId != null) { - file = await this.driveFilesRepository.findOneBy({ - id: ps.fileId, - userId: me.id, - }); - - if (file == null) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - // テキストが無いかつ添付ファイルも無かったらエラー - if (ps.text == null && file == null) { - throw new ApiError(meta.errors.contentRequired); - } - - return await this.messagingService.createMessage(me, recipientUser, recipientGroup, ps.text, file); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts deleted file mode 100644 index cd74f5f197..0000000000 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import ms from 'ms'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MessagingMessagesRepository } from '@/models/index.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['messaging'], - - requireCredential: true, - - kind: 'write:messaging', - - limit: { - duration: ms('1hour'), - max: 300, - minInterval: ms('1sec'), - }, - - errors: { - noSuchMessage: { - message: 'No such message.', - code: 'NO_SUCH_MESSAGE', - id: '54b5b326-7925-42cf-8019-130fda8b56af', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - messageId: { type: 'string', format: 'misskey:id' }, - }, - required: ['messageId'], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private messagingService: MessagingService, - ) { - super(meta, paramDef, async (ps, me) => { - const message = await this.messagingMessagesRepository.findOneBy({ - id: ps.messageId, - userId: me.id, - }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); - } - - await this.messagingService.deleteMessage(message); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts deleted file mode 100644 index bddb6d932d..0000000000 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { MessagingMessagesRepository } from '@/models/index.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['messaging'], - - requireCredential: true, - - kind: 'write:messaging', - - errors: { - noSuchMessage: { - message: 'No such message.', - code: 'NO_SUCH_MESSAGE', - id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - messageId: { type: 'string', format: 'misskey:id' }, - }, - required: ['messageId'], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private messagingService: MessagingService, - ) { - super(meta, paramDef, async (ps, me) => { - const message = await this.messagingMessagesRepository.findOneBy({ id: ps.messageId }); - - if (message == null) { - throw new ApiError(meta.errors.noSuchMessage); - } - - if (message.recipientId) { - await this.messagingService.readUserMessagingMessage(me.id, message.userId, [message.id]).catch(err => { - if (err.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage); - throw err; - }); - } else if (message.groupId) { - await this.messagingService.readGroupMessagingMessage(me.id, message.groupId, [message.id]).catch(err => { - if (err.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage); - throw err; - }); - } - }); - } -} diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts index 198fc190d4..6ca7bdf623 100644 --- a/packages/backend/src/server/api/stream/ChannelsService.ts +++ b/packages/backend/src/server/api/stream/ChannelsService.ts @@ -1,5 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; import { HybridTimelineChannelService } from './channels/hybrid-timeline.js'; import { LocalTimelineChannelService } from './channels/local-timeline.js'; import { HomeTimelineChannelService } from './channels/home-timeline.js'; @@ -11,11 +12,8 @@ import { ServerStatsChannelService } from './channels/server-stats.js'; import { QueueStatsChannelService } from './channels/queue-stats.js'; import { UserListChannelService } from './channels/user-list.js'; import { AntennaChannelService } from './channels/antenna.js'; -import { MessagingChannelService } from './channels/messaging.js'; -import { MessagingIndexChannelService } from './channels/messaging-index.js'; import { DriveChannelService } from './channels/drive.js'; import { HashtagChannelService } from './channels/hashtag.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ChannelsService { @@ -29,8 +27,6 @@ export class ChannelsService { private hashtagChannelService: HashtagChannelService, private antennaChannelService: AntennaChannelService, private channelChannelService: ChannelChannelService, - private messagingChannelService: MessagingChannelService, - private messagingIndexChannelService: MessagingIndexChannelService, private driveChannelService: DriveChannelService, private serverStatsChannelService: ServerStatsChannelService, private queueStatsChannelService: QueueStatsChannelService, @@ -50,8 +46,6 @@ export class ChannelsService { case 'hashtag': return this.hashtagChannelService; case 'antenna': return this.antennaChannelService; case 'channel': return this.channelChannelService; - case 'messaging': return this.messagingChannelService; - case 'messagingIndex': return this.messagingIndexChannelService; case 'drive': return this.driveChannelService; case 'serverStats': return this.serverStatsChannelService; case 'queueStats': return this.queueStatsChannelService; diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 5ba84e43c4..589c294372 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -1,32 +1,25 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, UsersRepository } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import type { User } from '@/models/entities/User.js'; import type { Packed } from '@/misc/schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; -import type { StreamMessages } from '../types.js'; class ChannelChannel extends Channel { public readonly chName = 'channel'; public static shouldShare = false; public static requireCredential = false; private channelId: string; - private typers: Record<User['id'], Date> = {}; - private emitTypersIntervalId: ReturnType<typeof setInterval>; constructor( private noteEntityService: NoteEntityService, - private userEntityService: UserEntityService, id: string, connection: Channel['connection'], ) { super(id, connection); //this.onNote = this.onNote.bind(this); - //this.emitTypers = this.emitTypers.bind(this); } @bindThis @@ -35,8 +28,6 @@ class ChannelChannel extends Channel { // Subscribe stream this.subscriber.on('notesStream', this.onNote); - this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent); - this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); } @bindThis @@ -67,41 +58,9 @@ class ChannelChannel extends Channel { } @bindThis - private onEvent(data: StreamMessages['channel']['payload']) { - if (data.type === 'typing') { - const id = data.body; - const begin = this.typers[id] == null; - this.typers[id] = new Date(); - if (begin) { - this.emitTypers(); - } - } - } - - @bindThis - private async emitTypers() { - const now = new Date(); - - // Remove not typing users - for (const [userId, date] of Object.entries(this.typers)) { - if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; - } - - const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); - - this.send({ - type: 'typers', - body: users, - }); - } - - @bindThis public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); - this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent); - - clearInterval(this.emitTypersIntervalId); } } @@ -112,7 +71,6 @@ export class ChannelChannelService { constructor( private noteEntityService: NoteEntityService, - private userEntityService: UserEntityService, ) { } @@ -120,7 +78,6 @@ export class ChannelChannelService { public create(id: string, connection: Channel['connection']): ChannelChannel { return new ChannelChannel( this.noteEntityService, - this.userEntityService, id, connection, ); diff --git a/packages/backend/src/server/api/stream/channels/messaging-index.ts b/packages/backend/src/server/api/stream/channels/messaging-index.ts deleted file mode 100644 index 66cb79f7a7..0000000000 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { bindThis } from '@/decorators.js'; -import Channel from '../channel.js'; - -class MessagingIndexChannel extends Channel { - public readonly chName = 'messagingIndex'; - public static shouldShare = true; - public static requireCredential = true; - - @bindThis - public async init(params: any) { - // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { - this.send(data); - }); - } -} - -@Injectable() -export class MessagingIndexChannelService { - public readonly shouldShare = MessagingIndexChannel.shouldShare; - public readonly requireCredential = MessagingIndexChannel.requireCredential; - - constructor( - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): MessagingIndexChannel { - return new MessagingIndexChannel( - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts deleted file mode 100644 index b544e297c5..0000000000 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import type { UserGroupJoiningsRepository, UsersRepository, MessagingMessagesRepository } from '@/models/index.js'; -import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js'; -import type { UserGroup } from '@/models/entities/UserGroup.js'; -import { MessagingService } from '@/core/MessagingService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { DI } from '@/di-symbols.js'; -import { bindThis } from '@/decorators.js'; -import Channel from '../channel.js'; -import type { StreamMessages } from '../types.js'; - -class MessagingChannel extends Channel { - public readonly chName = 'messaging'; - public static shouldShare = false; - public static requireCredential = true; - - private otherpartyId: string | null; - private otherparty: User | null; - private groupId: string | null; - private subCh: `messagingStream:${User['id']}-${User['id']}` | `messagingStream:${UserGroup['id']}`; - private typers: Record<User['id'], Date> = {}; - private emitTypersIntervalId: ReturnType<typeof setInterval>; - - constructor( - private usersRepository: UsersRepository, - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - private messagingMessagesRepository: MessagingMessagesRepository, - private userEntityService: UserEntityService, - private messagingService: MessagingService, - - id: string, - connection: Channel['connection'], - ) { - super(id, connection); - //this.onEvent = this.onEvent.bind(this); - //this.onMessage = this.onMessage.bind(this); - //this.emitTypers = this.emitTypers.bind(this); - } - - @bindThis - public async init(params: any) { - this.otherpartyId = params.otherparty; - this.otherparty = this.otherpartyId ? await this.usersRepository.findOneByOrFail({ id: this.otherpartyId }) : null; - this.groupId = params.group; - - // Check joining - if (this.groupId) { - const joining = await this.userGroupJoiningsRepository.findOneBy({ - userId: this.user!.id, - userGroupId: this.groupId, - }); - - if (joining == null) { - return; - } - } - - this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); - - this.subCh = this.otherpartyId - ? `messagingStream:${this.user!.id}-${this.otherpartyId}` - : `messagingStream:${this.groupId}`; - - // Subscribe messaging stream - this.subscriber.on(this.subCh, this.onEvent); - } - - @bindThis - private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { - if (data.type === 'typing') { - const id = data.body; - const begin = this.typers[id] == null; - this.typers[id] = new Date(); - if (begin) { - this.emitTypers(); - } - } else { - this.send(data); - } - } - - @bindThis - public onMessage(type: string, body: any) { - switch (type) { - case 'read': - if (this.otherpartyId) { - this.messagingService.readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]); - - // リモートユーザーからのメッセージだったら既読配信 - if (this.userEntityService.isLocalUser(this.user!) && this.userEntityService.isRemoteUser(this.otherparty!)) { - this.messagingMessagesRepository.findOneBy({ id: body.id }).then(message => { - if (message) this.messagingService.deliverReadActivity(this.user as LocalUser, this.otherparty as RemoteUser, message); - }); - } - } else if (this.groupId) { - this.messagingService.readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]); - } - break; - } - } - - @bindThis - private async emitTypers() { - const now = new Date(); - - // Remove not typing users - for (const [userId, date] of Object.entries(this.typers)) { - if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; - } - - const users = await this.userEntityService.packMany(Object.keys(this.typers), null, { detail: false }); - - this.send({ - type: 'typers', - body: users, - }); - } - - @bindThis - public dispose() { - this.subscriber.off(this.subCh, this.onEvent); - - clearInterval(this.emitTypersIntervalId); - } -} - -@Injectable() -export class MessagingChannelService { - public readonly shouldShare = MessagingChannel.shouldShare; - public readonly requireCredential = MessagingChannel.requireCredential; - - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.userGroupJoiningsRepository) - private userGroupJoiningsRepository: UserGroupJoiningsRepository, - - @Inject(DI.messagingMessagesRepository) - private messagingMessagesRepository: MessagingMessagesRepository, - - private userEntityService: UserEntityService, - private messagingService: MessagingService, - ) { - } - - @bindThis - public create(id: string, connection: Channel['connection']): MessagingChannel { - return new MessagingChannel( - this.usersRepository, - this.userGroupJoiningsRepository, - this.messagingMessagesRepository, - this.userEntityService, - this.messagingService, - id, - connection, - ); - } -} diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 6763953f9d..fea06c0315 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -147,12 +147,6 @@ export default class Connection { case 'disconnect': this.onChannelDisconnectRequested(body); break; case 'channel': this.onChannelMessageRequested(body); break; case 'ch': this.onChannelMessageRequested(body); break; // alias - - // 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、 - // クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別 - // なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。 - case 'typingOnChannel': this.typingOnChannel(body.channel); break; - case 'typingOnMessaging': this.typingOnMessaging(body); break; } } @@ -326,24 +320,6 @@ export default class Connection { } @bindThis - private typingOnChannel(channel: ChannelModel['id']) { - if (this.user) { - this.globalEventService.publishChannelStream(channel, 'typing', this.user.id); - } - } - - @bindThis - private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) { - if (this.user) { - if (param.partner) { - this.globalEventService.publishMessagingStream(param.partner, this.user.id, 'typing', this.user.id); - } else if (param.group) { - this.globalEventService.publishGroupMessagingStream(param.group, 'typing', this.user.id); - } - } - } - - @bindThis private async updateFollowing() { const followings = await this.followingsRepository.find({ where: { diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 8bb4147b43..3d8cfea9d3 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -6,7 +6,6 @@ import type { Antenna } from '@/models/entities/Antenna.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { DriveFolder } from '@/models/entities/DriveFolder.js'; import type { UserList } from '@/models/entities/UserList.js'; -import type { MessagingMessage } from '@/models/entities/MessagingMessage.js'; import type { UserGroup } from '@/models/entities/UserGroup.js'; import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js'; import type { Signin } from '@/models/entities/Signin.js'; @@ -96,9 +95,6 @@ export interface MainStreamTypes { readAllUnreadMentions: undefined; unreadSpecifiedNote: Note['id']; readAllUnreadSpecifiedNotes: undefined; - readAllMessagingMessages: undefined; - messagingMessage: Packed<'MessagingMessage'>; - unreadMessagingMessage: Packed<'MessagingMessage'>; readAllAntennas: undefined; unreadAntenna: Antenna; readAllAnnouncements: undefined; @@ -153,10 +149,6 @@ type NoteStreamEventTypes = { }; }; -export interface ChannelStreamTypes { - typing: User['id']; -} - export interface UserListStreamTypes { userAdded: Packed<'User'>; userRemoved: Packed<'User'>; @@ -166,28 +158,6 @@ export interface AntennaStreamTypes { note: Note; } -export interface MessagingStreamTypes { - read: MessagingMessage['id'][]; - typing: User['id']; - message: Packed<'MessagingMessage'>; - deleted: MessagingMessage['id']; -} - -export interface GroupMessagingStreamTypes { - read: { - ids: MessagingMessage['id'][]; - userId: User['id']; - }; - typing: User['id']; - message: Packed<'MessagingMessage'>; - deleted: MessagingMessage['id']; -} - -export interface MessagingIndexStreamTypes { - read: MessagingMessage['id'][]; - message: Packed<'MessagingMessage'>; -} - export interface AdminStreamTypes { newAbuseUserReport: { id: AbuseUserReport['id']; @@ -242,10 +212,6 @@ export type StreamMessages = { name: `noteStream:${Note['id']}`; payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>; }; - channel: { - name: `channelStream:${Channel['id']}`; - payload: EventUnionFromDictionary<SerializedAll<ChannelStreamTypes>>; - }; userList: { name: `userListStream:${UserList['id']}`; payload: EventUnionFromDictionary<SerializedAll<UserListStreamTypes>>; @@ -254,18 +220,6 @@ export type StreamMessages = { name: `antennaStream:${Antenna['id']}`; payload: EventUnionFromDictionary<SerializedAll<AntennaStreamTypes>>; }; - messaging: { - name: `messagingStream:${User['id']}-${User['id']}`; - payload: EventUnionFromDictionary<SerializedAll<MessagingStreamTypes>>; - }; - groupMessaging: { - name: `messagingStream:${UserGroup['id']}`; - payload: EventUnionFromDictionary<SerializedAll<GroupMessagingStreamTypes>>; - }; - messagingIndex: { - name: `messagingIndexStream:${User['id']}`; - payload: EventUnionFromDictionary<SerializedAll<MessagingIndexStreamTypes>>; - }; admin: { name: `adminStream:${User['id']}`; payload: EventUnionFromDictionary<SerializedAll<AdminStreamTypes>>; diff --git a/packages/backend/test/_e2e/endpoints.ts b/packages/backend/test/_e2e/endpoints.ts index ea8433dfa2..aed980d6c8 100644 --- a/packages/backend/test/_e2e/endpoints.ts +++ b/packages/backend/test/_e2e/endpoints.ts @@ -778,63 +778,6 @@ describe('API: Endpoints', () => { })); }); - describe('messaging/messages/create', () => { - test('メッセージを送信できる', async () => { - const res = await api('/messaging/messages/create', { - userId: bob.id, - text: 'test' - }, alice); - - assert.strictEqual(res.status, 200); - assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.text, 'test'); - })); - - test('自分自身にはメッセージを送信できない', async () => { - const res = await api('/messaging/messages/create', { - userId: alice.id, - text: 'Yo' - }, alice); - - assert.strictEqual(res.status, 400); - })); - - test('存在しないユーザーにはメッセージを送信できない', async () => { - const res = await api('/messaging/messages/create', { - userId: '000000000000000000000000', - text: 'test' - }, alice); - - assert.strictEqual(res.status, 400); - })); - - test('不正なユーザーIDで怒られる', async () => { - const res = await api('/messaging/messages/create', { - userId: 'foo', - text: 'test' - }, alice); - - assert.strictEqual(res.status, 400); - })); - - test('テキストが無くて怒られる', async () => { - const res = await api('/messaging/messages/create', { - userId: bob.id - }, alice); - - assert.strictEqual(res.status, 400); - })); - - test('文字数オーバーで怒られる', async () => { - const res = await api('/messaging/messages/create', { - userId: bob.id, - text: '!'.repeat(1001) - }, alice); - - assert.strictEqual(res.status, 400); - })); - }); - describe('notes/replies', () => { test('自分に閲覧権限のない投稿は含まれない', async () => { const alicePost = await post(alice, { diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 620b126822..77d5adc23e 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -160,12 +160,6 @@ let hasNotSpecifiedMentions = $ref(false); let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') || '[]')); let imeText = $ref(''); -const typing = throttle(3000, () => { - if (props.channel) { - stream.send('typingOnChannel', { channel: props.channel.id }); - } -}); - const draftKey = $computed((): string => { let key = props.channel ? `channel:${props.channel.id}` : ''; @@ -447,12 +441,10 @@ function clear() { function onKeydown(ev: KeyboardEvent) { if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post(); if (ev.which === 27) emit('esc'); - typing(); } function onCompositionUpdate(ev: CompositionEvent) { imeText = ev.data; - typing(); } function onCompositionEnd(ev: CompositionEvent) { diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 64c252ce55..b013b376fb 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -505,15 +505,6 @@ if ($i) { updateAccount({ hasUnreadSpecifiedNotes: false }); }); - main.on('readAllMessagingMessages', () => { - updateAccount({ hasUnreadMessagingMessage: false }); - }); - - main.on('unreadMessagingMessage', () => { - updateAccount({ hasUnreadMessagingMessage: true }); - sound.play('chatBg'); - }); - main.on('readAllAntennas', () => { updateAccount({ hasUnreadAntenna: false }); }); diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 4f809d888e..b85299b07c 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -15,13 +15,6 @@ export const navbarItemDef = reactive({ indicated: computed(() => $i != null && $i.hasUnreadNotification), to: '/my/notifications', }, - messaging: { - title: i18n.ts.messaging, - icon: 'ti ti-messages', - show: computed(() => $i != null), - indicated: computed(() => $i != null && $i.hasUnreadMessagingMessage), - to: '/my/messaging', - }, drive: { title: i18n.ts.drive, icon: 'ti ti-cloud', diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue deleted file mode 100644 index 3d11cf13e9..0000000000 --- a/packages/frontend/src/pages/messaging/index.vue +++ /dev/null @@ -1,305 +0,0 @@ -<template> -<MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> - <div class="yweeujhr"> - <MkButton primary class="start" @click="start"><i class="ti ti-plus"></i> {{ $ts.startMessaging }}</MkButton> - - <div v-if="messages.length > 0" class="history"> - <MkA - v-for="(message, i) in messages" - :key="message.id" - v-anim="i" - class="message _panel" - :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }" - :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" - :data-index="i" - > - <div> - <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/> - <header v-if="message.groupId"> - <span class="name">{{ message.group.name }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <header v-else> - <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> - <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <div class="body"> - <p class="text"><span v-if="isMe(message)" class="me">{{ $ts.you }}:</span>{{ message.text }}</p> - </div> - </div> - </MkA> - </div> - <div v-if="!fetching && messages.length == 0" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noHistory }}</div> - </div> - <MkLoading v-if="fetching"/> - </div> - </MkSpacer> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { defineAsyncComponent, defineComponent, inject, markRaw, onMounted, onUnmounted } from 'vue'; -import * as Acct from 'misskey-js/built/acct'; -import MkButton from '@/components/MkButton.vue'; -import { acct } from '@/filters/user'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { useRouter } from '@/router'; -import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; -import { $i } from '@/account'; - -const router = useRouter(); - -let fetching = $ref(true); -let moreFetching = $ref(false); -let messages = $ref([]); -let connection = $ref(null); - -const getAcct = Acct.toString; - -function isMe(message) { - return message.userId === $i.id; -} - -function onMessage(message) { - if (message.recipientId) { - messages = messages.filter(m => !( - (m.recipientId === message.recipientId && m.userId === message.userId) || - (m.recipientId === message.userId && m.userId === message.recipientId))); - - messages.unshift(message); - } else if (message.groupId) { - messages = messages.filter(m => m.groupId !== message.groupId); - messages.unshift(message); - } -} - -function onRead(ids) { - for (const id of ids) { - const found = messages.find(m => m.id === id); - if (found) { - if (found.recipientId) { - found.isRead = true; - } else if (found.groupId) { - found.reads.push($i.id); - } - } - } -} - -function start(ev) { - os.popupMenu([{ - text: i18n.ts.messagingWithUser, - icon: 'ti ti-user', - action: () => { startUser(); }, - }, { - text: i18n.ts.messagingWithGroup, - icon: 'ti ti-users', - action: () => { startGroup(); }, - }], ev.currentTarget ?? ev.target); -} - -async function startUser() { - os.selectUser().then(user => { - router.push(`/my/messaging/${Acct.toString(user)}`); - }); -} - -async function startGroup() { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); - if (groups1.length === 0 && groups2.length === 0) { - os.alert({ - type: 'warning', - title: i18n.ts.youHaveNoGroups, - text: i18n.ts.joinOrCreateGroup, - }); - return; - } - const { canceled, result: group } = await os.select({ - title: i18n.ts.group, - items: groups1.concat(groups2).map(group => ({ - value: group, text: group.name, - })), - }); - if (canceled) return; - router.push(`/my/messaging/group/${group.id}`); -} - -onMounted(() => { - connection = markRaw(stream.useChannel('messagingIndex')); - - connection.on('message', onMessage); - connection.on('read', onRead); - - os.api('messaging/history', { group: false }).then(userMessages => { - os.api('messaging/history', { group: true }).then(groupMessages => { - const _messages = userMessages.concat(groupMessages); - _messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - messages = _messages; - fetching = false; - }); - }); -}); - -onUnmounted(() => { - if (connection) connection.dispose(); -}); - -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - -definePageMetadata({ - title: i18n.ts.messaging, - icon: 'ti ti-messages', -}); -</script> - -<style lang="scss" scoped> -.yweeujhr { - - > .start { - margin: 0 auto var(--margin) auto; - } - - > .history { - > .message { - display: block; - text-decoration: none; - margin-bottom: var(--margin); - - * { - pointer-events: none; - user-select: none; - } - - &:hover { - .avatar { - filter: saturate(200%); - } - } - - &:active { - } - - &.isRead, - &.isMe { - opacity: 0.8; - } - - &:not(.isMe):not(.isRead) { - > div { - background-image: url("/client-assets/unread.svg"); - background-repeat: no-repeat; - background-position: 0 center; - } - } - - &:after { - content: ""; - display: block; - clear: both; - } - - > div { - padding: 20px 30px; - - &:after { - content: ""; - display: block; - clear: both; - } - - > header { - display: flex; - align-items: center; - margin-bottom: 2px; - white-space: nowrap; - overflow: hidden; - - > .name { - margin: 0; - padding: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 1em; - font-weight: bold; - transition: all 0.1s ease; - } - - > .username { - margin: 0 8px; - } - - > .time { - margin: 0 0 0 auto; - } - } - - > .avatar { - float: left; - width: 54px; - height: 54px; - margin: 0 16px 0 0; - border-radius: 8px; - transition: all 0.1s ease; - } - - > .body { - - > .text { - display: block; - margin: 0 0 0 0; - padding: 0; - overflow: hidden; - overflow-wrap: break-word; - font-size: 1.1em; - color: var(--faceText); - - .me { - opacity: 0.7; - } - } - - > .image { - display: block; - max-width: 100%; - max-height: 512px; - } - } - } - } - } -} - -@container (max-width: 400px) { - .yweeujhr { - > .history { - > .message { - &:not(.isMe):not(.isRead) { - > div { - background-image: none; - border-left: solid 4px #3aa2dc; - } - } - - > div { - padding: 16px; - font-size: 0.9em; - - > .avatar { - margin: 0 12px 0 0; - } - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue deleted file mode 100644 index d6113668dd..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.form.vue +++ /dev/null @@ -1,366 +0,0 @@ -<template> -<div - :class="$style['root']" - @dragover.stop="onDragover" - @drop.stop="onDrop" -> - <textarea - :class="$style['textarea']" - class="_acrylic" - ref="textEl" - v-model="text" - :placeholder="i18n.ts.inputMessageHere" - @keydown="onKeydown" - @compositionupdate="onCompositionUpdate" - @paste="onPaste" - ></textarea> - <footer :class="$style['footer']"> - <div v-if="file" :class="$style['file']" @click="file = null">{{ file.name }}</div> - <div :class="$style['buttons']"> - <button class="_button" :class="$style['button']" @click="chooseFile"><i class="ti ti-photo-plus"></i></button> - <button class="_button" :class="$style['button']" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button> - <button class="_button" :class="[$style['button'], $style['send']]" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send"> - <template v-if="!sending"><i class="ti ti-send"></i></template><template v-if="sending"><MkLoading :em="true"/></template> - </button> - </div> - </footer> - <input :class="$style['file-input']" ref="fileEl" type="file" @change="onChangeFile"/> -</div> -</template> - -<script lang="ts" setup> -import { onMounted, watch } from 'vue'; -import * as Misskey from 'misskey-js'; -import autosize from 'autosize'; -//import insertTextAtCursor from 'insert-text-at-cursor'; -import { throttle } from 'throttle-debounce'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { selectFile } from '@/scripts/select-file'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; -//import { Autocomplete } from '@/scripts/autocomplete'; -import { uploadFile } from '@/scripts/upload'; -import { miLocalStorage } from '@/local-storage'; - -const props = defineProps<{ - user?: Misskey.entities.UserDetailed | null; - group?: Misskey.entities.UserGroup | null; -}>(); - -let textEl = $shallowRef<HTMLTextAreaElement>(); -let fileEl = $shallowRef<HTMLInputElement>(); - -let text = $ref<string>(''); -let file = $ref<Misskey.entities.DriveFile | null>(null); -let sending = $ref(false); -const typing = throttle(3000, () => { - stream.send('typingOnMessaging', props.user ? { partner: props.user.id } : { group: props.group?.id }); -}); - -let draftKey = $computed(() => props.user ? 'user:' + props.user.id : 'group:' + props.group?.id); -let canSend = $computed(() => (text != null && text !== '') || file != null); - -watch([$$(text), $$(file)], saveDraft); - -async function onPaste(ev: ClipboardEvent) { - if (!ev.clipboardData) return; - - const clipboardData = ev.clipboardData; - const items = clipboardData.items; - - if (items.length === 1) { - if (items[0].kind === 'file') { - const pastedFile = items[0].getAsFile(); - if (!pastedFile) return; - const lio = pastedFile.name.lastIndexOf('.'); - const ext = lio >= 0 ? pastedFile.name.slice(lio) : ''; - const formatted = formatTimeString(new Date(pastedFile.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1') + ext; - if (formatted) upload(pastedFile, formatted); - } - } else { - if (items[0].kind === 'file') { - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - } - } -} - -function onDragover(ev: DragEvent) { - if (!ev.dataTransfer) return; - - const isFile = ev.dataTransfer.items[0].kind === 'file'; - const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - ev.preventDefault(); - switch (ev.dataTransfer.effectAllowed) { - case 'all': - case 'uninitialized': - case 'copy': - case 'copyLink': - case 'copyMove': - ev.dataTransfer.dropEffect = 'copy'; - break; - case 'linkMove': - case 'move': - ev.dataTransfer.dropEffect = 'move'; - break; - default: - ev.dataTransfer.dropEffect = 'none'; - break; - } - } -} - -function onDrop(ev: DragEvent): void { - if (!ev.dataTransfer) return; - - // ファイルだったら - if (ev.dataTransfer.files.length === 1) { - ev.preventDefault(); - upload(ev.dataTransfer.files[0]); - return; - } else if (ev.dataTransfer.files.length > 1) { - ev.preventDefault(); - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - return; - } - - //#region ドライブのファイル - const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile !== '') { - file = JSON.parse(driveFile); - ev.preventDefault(); - } - //#endregion -} - -function onKeydown(ev: KeyboardEvent) { - typing(); - if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey) && canSend) { - send(); - } -} - -function onCompositionUpdate() { - typing(); -} - -function chooseFile(ev: MouseEvent) { - selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(selectedFile => { - file = selectedFile; - }); -} - -function onChangeFile() { - if (fileEl.files![0]) upload(fileEl.files[0]); -} - -function upload(fileToUpload: File, name?: string) { - uploadFile(fileToUpload, defaultStore.state.uploadFolder, name).then(res => { - file = res; - }); -} - -function send() { - sending = true; - os.api('messaging/messages/create', { - userId: props.user ? props.user.id : undefined, - groupId: props.group ? props.group.id : undefined, - text: text ? text : undefined, - fileId: file ? file.id : undefined, - }).then(message => { - clear(); - }).catch(err => { - console.error(err); - }).then(() => { - sending = false; - }); -} - -function clear() { - text = ''; - file = null; - deleteDraft(); -} - -function saveDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); - - drafts[draftKey] = { - updatedAt: new Date(), - // eslint-disable-next-line id-denylist - data: { - text: text, - file: file, - }, - }; - - miLocalStorage.setItem('message_drafts', JSON.stringify(drafts)); -} - -function deleteDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); - - delete drafts[draftKey]; - - miLocalStorage.setItem('message_drafts', JSON.stringify(drafts)); -} - -async function insertEmoji(ev: MouseEvent) { - os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl); -} - -onMounted(() => { - autosize(textEl); - - // TODO: detach when unmount - // TODO - //new Autocomplete(textEl, this, { model: 'text' }); - - // 書きかけの投稿を復元 - const draft = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}')[draftKey]; - if (draft) { - text = draft.data.text; - file = draft.data.file; - } -}); - -defineExpose({ - file, - upload, -}); -</script> - -<style lang="scss" module> -.root { - position: relative; -} - -.textarea { - cursor: auto; - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 80px; - margin: 0; - padding: 16px 16px 0 16px; - resize: none; - font-size: 1em; - font-family: inherit; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - box-sizing: border-box; - color: var(--fg); -} - -.footer { - position: sticky; - bottom: 0; - background: var(--panel); -} - -.file { - padding: 8px; - color: var(--fg); - background: transparent; - cursor: pointer; -} -/* -.files { - display: block; - margin: 0; - padding: 0 8px; - list-style: none; - - &:after { - content: ''; - display: block; - clear: both; - } - - > li { - display: block; - float: left; - margin: 4px; - padding: 0; - width: 64px; - height: 64px; - background-color: #eee; - background-repeat: no-repeat; - background-position: center center; - background-size: cover; - cursor: move; - - &:hover { - > .remove { - display: block; - } - } - } -} - -.file-remove { - display: none; - position: absolute; - right: -6px; - top: -6px; - margin: 0; - padding: 0; - background: transparent; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - cursor: pointer; -} -*/ - -.buttons { - display: flex; -} - -.button { - margin: 0; - padding: 16px; - font-size: 1em; - font-weight: normal; - text-decoration: none; - transition: color 0.1s ease; - - &:hover { - color: var(--accent); - } - - &:active { - color: var(--accentDarken); - transition: color 0s ease; - } -} -.send { - margin-left: auto; - color: var(--accent); - - &:hover { - color: var(--accentLighten); - } - - &:active { - color: var(--accentDarken); - transition: color 0s ease; - } -} - -.file-input { - display: none; -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.message.vue b/packages/frontend/src/pages/messaging/messaging-room.message.vue deleted file mode 100644 index d10798b92e..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.message.vue +++ /dev/null @@ -1,338 +0,0 @@ -<template> -<div class="thvuemwp" :class="{ isMe }"> - <MkAvatar class="avatar" :user="message.user" indicator link preview/> - <div class="content"> - <div class="balloon" :class="{ noText: message.text == null }"> - <button v-if="isMe" class="delete-button" :title="$ts.delete" @click="del"> - <img src="/client-assets/remove.png" alt="Delete"/> - </button> - <div v-if="!message.isDeleted" class="content"> - <Mfm v-if="message.text" ref="text" class="text" :text="message.text" :i="$i"/> - <div v-if="message.file" class="file"> - <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> - <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> - <p v-else>{{ message.file.name }}</p> - </a> - </div> - </div> - <div v-else class="content"> - <p class="is-deleted">{{ $ts.deleted }}</p> - </div> - </div> - <div></div> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/> - <footer> - <template v-if="isGroup"> - <span v-if="message.reads.length > 0" class="read">{{ $ts.messageRead }} {{ message.reads.length }}</span> - </template> - <template v-else> - <span v-if="isMe && message.isRead" class="read">{{ $ts.messageRead }}</span> - </template> - <MkTime :time="message.createdAt"/> - <template v-if="message.is_edited"><i class="ti ti-pencil"></i></template> - </footer> - </div> -</div> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import * as mfm from 'mfm-js'; -import * as Misskey from 'misskey-js'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import MkUrlPreview from '@/components/MkUrlPreview.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; - -const props = defineProps<{ - message: Misskey.entities.MessagingMessage; - isGroup?: boolean; -}>(); - -const isMe = $computed(() => props.message.userId === $i?.id); -const urls = $computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []); - -function del(): void { - os.api('messaging/messages/delete', { - messageId: props.message.id, - }); -} -</script> - -<style lang="scss" scoped> -.thvuemwp { - $me-balloon-color: var(--accent); - - position: relative; - background-color: transparent; - display: flex; - - > .avatar { - position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); - display: block; - width: 54px; - height: 54px; - transition: all 0.1s ease; - } - - > .content { - min-width: 0; - - > .balloon { - position: relative; - display: inline-flex; - align-items: center; - padding: 0; - min-height: 38px; - border-radius: 16px; - max-width: 100%; - - &:before { - content: ""; - pointer-events: none; - display: block; - position: absolute; - top: 12px; - } - - & + * { - clear: both; - } - - &:hover { - > .delete-button { - display: block; - } - } - - > .delete-button { - display: none; - position: absolute; - z-index: 1; - top: -4px; - right: -4px; - margin: 0; - padding: 0; - cursor: pointer; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - background: transparent; - - > img { - vertical-align: bottom; - width: 16px; - height: 16px; - cursor: pointer; - } - } - - > .content { - max-width: 100%; - - > .is-deleted { - display: block; - margin: 0; - padding: 0; - overflow: hidden; - overflow-wrap: break-word; - font-size: 1em; - color: rgba(#000, 0.5); - } - - > .text { - display: block; - margin: 0; - padding: 12px 18px; - overflow: hidden; - overflow-wrap: break-word; - word-break: break-word; - font-size: 1em; - color: rgba(#000, 0.8); - - & + .file { - > a { - border-radius: 0 0 16px 16px; - } - } - } - - > .file { - > a { - display: block; - max-width: 100%; - border-radius: 16px; - overflow: hidden; - text-decoration: none; - - &:hover { - text-decoration: none; - - > p { - background: #ccc; - } - } - - > * { - display: block; - margin: 0; - width: 100%; - max-height: 512px; - object-fit: contain; - box-sizing: border-box; - } - - > p { - padding: 30px; - text-align: center; - color: #555; - background: #ddd; - } - } - } - } - } - - > footer { - display: block; - margin: 2px 0 0 0; - font-size: 0.65em; - - > .read { - margin: 0 8px; - } - - > i { - margin-left: 4px; - } - } - } - - &:not(.isMe) { - padding-left: var(--margin); - - > .content { - padding-left: 16px; - padding-right: 32px; - - > .balloon { - $color: var(--messageBg); - background: $color; - - &.noText { - background: transparent; - } - - &:not(.noText):before { - left: -14px; - border-top: solid 8px transparent; - border-right: solid 8px $color; - border-bottom: solid 8px transparent; - border-left: solid 8px transparent; - } - - > .content { - > .text { - color: var(--fg); - } - } - } - - > footer { - text-align: left; - } - } - } - - &.isMe { - flex-direction: row-reverse; - padding-right: var(--margin); - right: var(--margin); // 削除時にposition: absoluteになったときに使う - - > .content { - padding-right: 16px; - padding-left: 32px; - text-align: right; - - > .balloon { - background: $me-balloon-color; - text-align: left; - - ::selection { - color: var(--accent); - background-color: #fff; - } - - &.noText { - background: transparent; - } - - &:not(.noText):before { - right: -14px; - left: auto; - border-top: solid 8px transparent; - border-right: solid 8px transparent; - border-bottom: solid 8px transparent; - border-left: solid 8px $me-balloon-color; - } - - > .content { - - > p.is-deleted { - color: rgba(#fff, 0.5); - } - - > .text { - &, ::v-deep(*) { - color: var(--fgOnAccent) !important; - } - } - } - } - - > footer { - text-align: right; - - > .read { - user-select: none; - } - } - } - } -} - -@container (max-width: 400px) { - .thvuemwp { - > .avatar { - width: 48px; - height: 48px; - } - - > .content { - > .balloon { - > .content { - > .text { - font-size: 0.9em; - } - } - } - } - } -} - -@container (max-width: 500px) { - .thvuemwp { - > .content { - > .balloon { - > .content { - > .text { - padding: 8px 16px; - } - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue deleted file mode 100644 index 0867f003a3..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.vue +++ /dev/null @@ -1,415 +0,0 @@ -<template> -<MkStickyContainer> -<template #header> - <MkPageHeader /> -</template> -<div - ref="rootEl" - :class="$style['root']" - @dragover.prevent.stop="onDragover" - @drop.prevent.stop="onDrop" -> - <div :class="$style['body']"> - <MkPagination v-if="pagination" ref="pagingComponent" :key="userAcct || groupId" :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ i18n.ts.noMessagesYet }}</div> - </div> - </template> - <template #default="{ items: messages, fetching: pFetching }"> - <MkDateSeparatedList - v-if="messages.length > 0" - v-slot="{ item: message }" - :class="{ [$style['messages']]: true, 'deny-move-transition': pFetching }" - :items="messages" - direction="up" - reversed - > - <XMessage :key="message.id" :message="message" :is-group="group != null"/> - </MkDateSeparatedList> - </template> - </MkPagination> - </div> - <footer :class="$style['footer']"> - <div v-if="typers.length > 0" :class="$style['typers']"> - <I18n :src="i18n.ts.typingUsers" text-tag="span"> - <template #users> - <b v-for="typer in typers" :key="typer.id" :class="$style['user']">{{ typer.username }}</b> - </template> - </I18n> - <MkEllipsis/> - </div> - <Transition :name="animation ? 'fade' : ''"> - <div v-show="showIndicator" :class="$style['new-message']"> - <button class="_buttonPrimary" @click="onIndicatorClick" :class="$style['new-message-button']"> - <i class="fas ti-fw fa-arrow-circle-down" :class="$style['new-message-icon']"></i>{{ i18n.ts.newMessageExists }} - </button> - </div> - </Transition> - <XForm v-if="!fetching" ref="formEl" :user="user" :group="group" :class="$style['form']"/> - </footer> -</div> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'; -import * as Misskey from 'misskey-js'; -import * as Acct from 'misskey-js/built/acct'; -import XMessage from './messaging-room.message.vue'; -import XForm from './messaging-room.form.vue'; -import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; -import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { defaultStore } from '@/store'; -import { definePageMetadata } from '@/scripts/page-metadata'; - -const props = defineProps<{ - userAcct?: string; - groupId?: string; -}>(); - -let rootEl = $shallowRef<HTMLDivElement>(); -let formEl = $shallowRef<InstanceType<typeof XForm>>(); -let pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>(); - -let fetching = $ref(true); -let user: Misskey.entities.UserDetailed | null = $ref(null); -let group: Misskey.entities.UserGroup | null = $ref(null); -let typers: Misskey.entities.User[] = $ref([]); -let connection: Misskey.ChannelConnection<Misskey.Channels['messaging']> | null = $ref(null); -let showIndicator = $ref(false); -const { - animation, -} = defaultStore.reactiveState; - -let pagination: Paging | null = $ref(null); - -watch([() => props.userAcct, () => props.groupId], () => { - if (connection) connection.dispose(); - fetch(); -}); - -async function fetch() { - fetching = true; - - if (props.userAcct) { - const acct = Acct.parse(props.userAcct); - user = await os.api('users/show', { username: acct.username, host: acct.host || undefined }); - group = null; - - pagination = { - endpoint: 'messaging/messages', - limit: 20, - params: { - userId: user.id, - }, - reversed: true, - pageEl: $$(rootEl).value, - }; - connection = stream.useChannel('messaging', { - otherparty: user.id, - }); - } else { - user = null; - group = await os.api('users/groups/show', { groupId: props.groupId }); - - pagination = { - endpoint: 'messaging/messages', - limit: 20, - params: { - groupId: group?.id, - }, - reversed: true, - pageEl: $$(rootEl).value, - }; - connection = stream.useChannel('messaging', { - group: group?.id, - }); - } - - connection.on('message', onMessage); - connection.on('read', onRead); - connection.on('deleted', onDeleted); - connection.on('typers', _typers => { - typers = _typers.filter(u => u.id !== $i?.id); - }); - - document.addEventListener('visibilitychange', onVisibilitychange); - - nextTick(() => { - pagingComponent.inited.then(() => { - thisScrollToBottom(); - }); - window.setTimeout(() => { - fetching = false; - }, 300); - }); -} - -function onDragover(ev: DragEvent) { - if (!ev.dataTransfer) return; - - const isFile = ev.dataTransfer.items[0].kind === 'file'; - const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; - - if (isFile || isDriveFile) { - switch (ev.dataTransfer.effectAllowed) { - case 'all': - case 'uninitialized': - case 'copy': - case 'copyLink': - case 'copyMove': - ev.dataTransfer.dropEffect = 'copy'; - break; - case 'linkMove': - case 'move': - ev.dataTransfer.dropEffect = 'move'; - break; - default: - ev.dataTransfer.dropEffect = 'none'; - break; - } - } else { - ev.dataTransfer.dropEffect = 'none'; - } -} - -function onDrop(ev: DragEvent): void { - if (!ev.dataTransfer) return; - - // ファイルだったら - if (ev.dataTransfer.files.length === 1) { - formEl.upload(ev.dataTransfer.files[0]); - return; - } else if (ev.dataTransfer.files.length > 1) { - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - return; - } - - //#region ドライブのファイル - const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile !== '') { - const file = JSON.parse(driveFile); - formEl.file = file; - } - //#endregion -} - -function onMessage(message) { - sound.play('chat'); - - const _isBottom = isBottomVisible(rootEl, 64); - - pagingComponent.prepend(message); - if (message.userId !== $i?.id && !document.hidden) { - connection?.send('read', { - id: message.id, - }); - } - - if (_isBottom) { - // Scroll to bottom - nextTick(() => { - thisScrollToBottom(); - }); - } else if (message.userId !== $i?.id) { - // Notify - notifyNewMessage(); - } -} - -function onRead(x) { - if (user) { - if (!Array.isArray(x)) x = [x]; - for (const id of x) { - if (pagingComponent.items.some(y => y.id === id)) { - const exist = pagingComponent.items.map(y => y.id).indexOf(id); - pagingComponent.items[exist] = { - ...pagingComponent.items[exist], - isRead: true, - }; - } - } - } else if (group) { - for (const id of x.ids) { - if (pagingComponent.items.some(y => y.id === id)) { - const exist = pagingComponent.items.map(y => y.id).indexOf(id); - pagingComponent.items[exist] = { - ...pagingComponent.items[exist], - reads: [...pagingComponent.items[exist].reads, x.userId], - }; - } - } - } -} - -function onDeleted(id) { - const msg = pagingComponent.items.find(m => m.id === id); - if (msg) { - pagingComponent.items = pagingComponent.items.filter(m => m.id !== msg.id); - } -} - -function thisScrollToBottom() { - scrollToBottom($$(rootEl).value, { behavior: 'smooth' }); -} - -function onIndicatorClick() { - showIndicator = false; - thisScrollToBottom(); -} - -let scrollRemove: (() => void) | null = $ref(null); - -function notifyNewMessage() { - showIndicator = true; - - scrollRemove = onScrollBottom(rootEl, () => { - showIndicator = false; - scrollRemove = null; - }); -} - -function onVisibilitychange() { - if (document.hidden) return; - for (const message of pagingComponent.items) { - if (message.userId !== $i?.id && !message.isRead) { - connection?.send('read', { - id: message.id, - }); - } - } -} - -onMounted(() => { - fetch(); -}); - -onBeforeUnmount(() => { - connection?.dispose(); - document.removeEventListener('visibilitychange', onVisibilitychange); - if (scrollRemove) scrollRemove(); -}); - -definePageMetadata(computed(() => !fetching ? user ? { - userName: user, - avatar: user, -} : { - title: group?.name, - icon: 'ti ti-users', -} : null)); -</script> - -<style lang="scss" module> -.root { - display: content; -} - -.body { - min-height: 80%; -} - -.more { - display: block; - margin: 16px auto; - padding: 0 12px; - line-height: 24px; - color: #fff; - background: rgba(#000, 0.3); - border-radius: 12px; - &:hover { - background: rgba(#000, 0.4); - } - &:active { - background: rgba(#000, 0.5); - } - > i { - margin-right: 4px; - } -} - -.fetching { - cursor: wait; -} - -.messages { - padding: 16px 0 0; - - > * { - margin-bottom: 16px; - } -} - -.footer { - width: 100%; - position: sticky; - z-index: 2; - padding-top: 8px; - bottom: var(--minBottomSpacing); -} - -.new-message { - width: 100%; - padding-bottom: 8px; - text-align: center; -} - -.new-message-button { - display: inline-block; - margin: 0; - padding: 0 12px; - line-height: 32px; - font-size: 12px; - border-radius: 16px; -} - -.new-message-icon { - display: inline-block; - margin-right: 8px; -} - -.typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - color: var(--fgTransparentWeak); -} - - -.user + .user:before { - content: ", "; - font-weight: normal; -} - -.user:last-of-type:after { - content: " "; -} - -.form { - max-height: 12em; - overflow-y: scroll; - border-top: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.fade-enter-active, .fade-leave-active { - transition: opacity 0.1s; -} - -.fade-enter-from, .fade-leave-to { - transition: opacity 0.5s; - opacity: 0; -} -</style> diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index db32ee862c..05c7fb72e5 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -5,7 +5,6 @@ <div class="_gaps_m"> <FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> <FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink> - <FormLink @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink> </div> </FormSection> <FormSection> @@ -47,10 +46,6 @@ async function readAllUnreadNotes() { await os.api('i/read-all-unread-notes'); } -async function readAllMessagingMessages() { - await os.api('i/read-all-messaging-messages'); -} - async function readAllNotifications() { await os.api('notifications/mark-all-as-read'); } diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 9004262689..2aa2e0ab3d 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -421,19 +421,6 @@ export const routes = [{ component: page(() => import('./pages/achievements.vue')), loginRequired: true, }, { - name: 'messaging', - path: '/my/messaging', - component: page(() => import('./pages/messaging/index.vue')), - loginRequired: true, -}, { - path: '/my/messaging/:userAcct', - component: page(() => import('./pages/messaging/messaging-room.vue')), - loginRequired: true, -}, { - path: '/my/messaging/group/:groupId', - component: page(() => import('./pages/messaging/messaging-room.vue')), - loginRequired: true, -}, { path: '/my/drive/folder/:folder', component: page(() => import('./pages/drive.vue')), loginRequired: true, diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 6e7845f667..1ec4491273 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -236,23 +236,6 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif default: return null; } - case 'unreadMessagingMessage': - if (data.body.groupId === null) { - return [t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.body.user) }), { - icon: data.body.user.avatarUrl, - badge: iconUrl('messages'), - tag: `messaging:user:${data.body.userId}`, - data, - renotify: true, - }]; - } - return [t('_notification.youGotMessagingMessageFromGroup', { name: data.body.group?.name ?? '' }), { - icon: data.body.user.avatarUrl, - badge: iconUrl('messages'), - tag: `messaging:group:${data.body.groupId}`, - data, - renotify: true, - }]; case 'unreadAntennaNote': return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 7bcf4d5976..f56208d6d6 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -15,9 +15,9 @@ globalThis.addEventListener('activate', ev => { .then(cacheNames => Promise.all( cacheNames .filter((v) => v !== swLang.cacheName) - .map(name => caches.delete(name)) + .map(name => caches.delete(name)), )) - .then(() => self.clients.claim()) + .then(() => self.clients.claim()), ); }); @@ -34,7 +34,7 @@ globalThis.addEventListener('fetch', ev => { if (!isHTMLRequest) return; ev.respondWith( fetch(ev.request) - .catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 })) + .catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 })), ); }); @@ -42,14 +42,13 @@ globalThis.addEventListener('push', ev => { // クライアント取得 ev.waitUntil(self.clients.matchAll({ includeUncontrolled: true, - type: 'window' + type: 'window', }).then(async (clients: readonly WindowClient[]) => { const data: pushNotificationDataMap[keyof pushNotificationDataMap] = ev.data?.json(); switch (data.type) { // case 'driveFileCreated': case 'notification': - case 'unreadMessagingMessage': case 'unreadAntennaNote': // 1日以上経過している場合は無視 if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break; @@ -63,11 +62,6 @@ globalThis.addEventListener('push', ev => { if (n?.data?.type === 'notification') n.close(); } break; - case 'readAllMessagingMessages': - for (const n of await self.registration.getNotifications()) { - if (n?.data?.type === 'unreadMessagingMessage') n.close(); - } - break; case 'readAllAntennas': for (const n of await self.registration.getNotifications()) { if (n?.data?.type === 'unreadAntennaNote') n.close(); @@ -75,25 +69,14 @@ globalThis.addEventListener('push', ev => { break; case 'readNotifications': for (const n of await self.registration.getNotifications()) { - if (data.body?.notificationIds?.includes(n.data.body.id)) { + if (data.body.notificationIds.includes(n.data.body.id)) { n.close(); } } break; - case 'readAllMessagingMessagesOfARoom': - for (const n of await self.registration.getNotifications()) { - if (n.data.type === 'unreadMessagingMessage' - && ('userId' in data.body - ? data.body.userId === n.data.body.userId - : data.body.groupId === n.data.body.groupId) - ) { - n.close(); - } - } - break; case 'readAntenna': for (const n of await self.registration.getNotifications()) { - if (n?.data?.type === 'unreadAntennaNote' && data.body?.antennaId === n.data.body.antenna.id) { + if (n?.data?.type === 'unreadAntennaNote' && data.body.antennaId === n.data.body.antenna.id) { n.close(); } } @@ -174,9 +157,6 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv } } break; - case 'unreadMessagingMessage': - client = await swos.openChat(data.body, loginId); - break; case 'unreadAntennaNote': client = await swos.openAntenna(data.body.antenna.id, loginId); } @@ -207,7 +187,7 @@ globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['me // Cache Storage全削除 await caches.keys() .then(cacheNames => Promise.all( - cacheNames.map(name => caches.delete(name)) + cacheNames.map(name => caches.delete(name)), )); return; // TODO } diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts index 3b35de4079..5b53ddecac 100644 --- a/packages/sw/src/types.ts +++ b/packages/sw/src/types.ts @@ -13,15 +13,12 @@ export type SwMessage = { // Defined also @/core/PushNotificationService.ts#L12 type pushNotificationDataSourceMap = { notification: Misskey.entities.Notification; - unreadMessagingMessage: Misskey.entities.MessagingMessage; unreadAntennaNote: { antenna: { id: string, name: string }; note: Misskey.entities.Note; }; readNotifications: { notificationIds: string[] }; readAllNotifications: undefined; - readAllMessagingMessages: undefined; - readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string }; readAntenna: { antennaId: string }; readAllAntennas: undefined; }; |