This commit is contained in:
syuilo
2025-03-23 12:09:12 +09:00
parent b043455152
commit ba026cfcd5
5 changed files with 134 additions and 0 deletions

View File

@@ -255,6 +255,11 @@ export class ChatService {
await redisPipeline.exec(); await redisPipeline.exec();
} }
@bindThis
public findMessageById(messageId: MiChatMessage['id']) {
return this.chatMessagesRepository.findOneBy({ id: messageId });
}
@bindThis @bindThis
public findMyMessageById(userId: MiUser['id'], messageId: MiChatMessage['id']) { public findMyMessageById(userId: MiUser['id'], messageId: MiChatMessage['id']) {
return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId }); return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId });

View File

@@ -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 'users/update-memo' from './endpoints/users/update-memo.js';
export * as 'chat/messages/create' from './endpoints/chat/messages/create.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/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/messages/timeline' from './endpoints/chat/messages/timeline.js';
export * as 'chat/history' from './endpoints/chat/history.js'; export * as 'chat/history' from './endpoints/chat/history.js';
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js'; export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';

View File

@@ -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<typeof meta, typeof paramDef> { // 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);
});
}
}

View File

@@ -0,0 +1,55 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader>
<MkSpacer :contentMax="700">
<div v-if="initializing">
<MkLoading/>
</div>
<div v-else>
<XMessage :message="message" :user="message.fromUser"/>
</div>
</MkSpacer>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, computed, watch, onMounted, nextTick, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
import * as Misskey from 'misskey-js';
import XMessage from './room.message.vue';
import * as os from '@/os.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { ensureSignin } from '@/i.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
const props = defineProps<{
messageId?: string;
}>();
const initializing = ref(true);
const message = ref<Misskey.entities.ChatMessage>();
async function initialize() {
initializing.value = true;
message.value = await misskeyApi('chat/messages/show', {
messageId: props.messageId,
});
initializing.value = false;
}
onMounted(() => {
initialize();
});
definePage({
title: i18n.ts.chat,
});
</script>

View File

@@ -43,9 +43,19 @@ export const ROUTE_DEF = [{
}, { }, {
path: '/chat', path: '/chat',
component: page(() => import('@/pages/chat/home.vue')), component: page(() => import('@/pages/chat/home.vue')),
loginRequired: true,
}, { }, {
path: '/chat/user/:userId', path: '/chat/user/:userId',
component: page(() => import('@/pages/chat/room.vue')), 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', path: '/instance-info/:host',
component: page(() => import('@/pages/instance-info.vue')), component: page(() => import('@/pages/instance-info.vue')),