diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 84062e5e25..6056564ae0 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -255,6 +255,11 @@ export class ChatService { await redisPipeline.exec(); } + @bindThis + public findMessageById(messageId: MiChatMessage['id']) { + return this.chatMessagesRepository.findOneBy({ id: messageId }); + } + @bindThis public findMyMessageById(userId: MiUser['id'], messageId: MiChatMessage['id']) { return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId }); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index ba999e14fb..07080f86ff 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -398,6 +398,7 @@ export * as 'users/show' from './endpoints/users/show.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js'; export * as 'chat/messages/create' from './endpoints/chat/messages/create.js'; export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js'; +export * as 'chat/messages/show' from './endpoints/chat/messages/show.js'; export * as 'chat/messages/timeline' from './endpoints/chat/messages/timeline.js'; export * as 'chat/history' from './endpoints/chat/history.js'; export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js'; diff --git a/packages/backend/src/server/api/endpoints/chat/messages/show.ts b/packages/backend/src/server/api/endpoints/chat/messages/show.ts new file mode 100644 index 0000000000..1c3346baf0 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/chat/messages/show.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ChatService } from '@/core/ChatService.js'; +import { ChatMessageEntityService } from '@/core/entities/ChatMessageEntityService.js'; +import { ApiError } from '@/server/api/error.js'; +import { RoleService } from '@/core/RoleService.js'; + +export const meta = { + tags: ['chat'], + + requireCredential: true, + + kind: 'read:chat', + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessage', + }, + + errors: { + noSuchMessage: { + message: 'No such message.', + code: 'NO_SUCH_MESSAGE', + id: '3710865b-1848-4da9-8d61-cfed15510b93', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + messageId: { type: 'string', format: 'misskey:id' }, + }, + required: ['messageId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private chatService: ChatService, + private roleService: RoleService, + private chatMessageEntityService: ChatMessageEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const message = await this.chatService.findMessageById(ps.messageId); + if (message == null) { + throw new ApiError(meta.errors.noSuchMessage); + } + if (message.fromUserId !== me.id && message.toUserId !== me.id && !(await this.roleService.isModerator(me))) { + throw new ApiError(meta.errors.noSuchMessage); + } + return this.chatMessageEntityService.pack(message, me); + }); + } +} diff --git a/packages/frontend/src/pages/chat/message.vue b/packages/frontend/src/pages/chat/message.vue new file mode 100644 index 0000000000..e0d5a7d23f --- /dev/null +++ b/packages/frontend/src/pages/chat/message.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index 2538c15f41..0585a31fd1 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -43,9 +43,19 @@ export const ROUTE_DEF = [{ }, { path: '/chat', component: page(() => import('@/pages/chat/home.vue')), + loginRequired: true, }, { path: '/chat/user/:userId', component: page(() => import('@/pages/chat/room.vue')), + loginRequired: true, +}, { + path: '/chat/room/:roomId', + component: page(() => import('@/pages/chat/room.vue')), + loginRequired: true, +}, { + path: '/chat/messages/:messageId', + component: page(() => import('@/pages/chat/message.vue')), + loginRequired: true, }, { path: '/instance-info/:host', component: page(() => import('@/pages/instance-info.vue')),