wip
This commit is contained in:
parent
ba026cfcd5
commit
9e23531adc
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat41742707840715 {
|
||||
name = 'Chat41742707840715'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "chat_room_invitation" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "roomId" character varying(32) NOT NULL, CONSTRAINT "PK_9d489521a312dd28225672de2dc" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8552bb38e7ed038c5bdd398a38" ON "chat_room_invitation" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5f265075b215fc390a57523b12" ON "chat_room_invitation" ("roomId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_044f2a7962b8ee5bbfaa02e8a3" ON "chat_room_invitation" ("userId", "roomId") `);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_5f265075b215fc390a57523b12a" FOREIGN KEY ("roomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_5f265075b215fc390a57523b12a"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_044f2a7962b8ee5bbfaa02e8a3"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_5f265075b215fc390a57523b12"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8552bb38e7ed038c5bdd398a38"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_room_invitation"`);
|
||||
}
|
||||
}
|
|
@ -12,15 +12,16 @@ import { QueueService } from '@/core/QueueService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ChatMessageEntityService } from '@/core/entities/ChatMessageEntityService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomInvitationsRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChatService {
|
||||
|
@ -43,6 +44,9 @@ export class ChatService {
|
|||
@Inject(DI.chatRoomsRepository)
|
||||
private chatRoomsRepository: ChatRoomsRepository,
|
||||
|
||||
@Inject(DI.chatRoomInvitationsRepository)
|
||||
private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
|
||||
|
||||
@Inject(DI.chatRoomMembershipsRepository)
|
||||
private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
|
||||
|
||||
|
@ -50,7 +54,7 @@ export class ChatService {
|
|||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private chatMessageEntityService: ChatMessageEntityService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private apRendererService: ApRendererService,
|
||||
|
@ -64,7 +68,7 @@ export class ChatService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async createMessage(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: {
|
||||
public async createMessageToUser(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: {
|
||||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
|
@ -139,61 +143,38 @@ export class ChatService {
|
|||
});
|
||||
}
|
||||
|
||||
const packedMessage = await this.chatMessageEntityService.packLite(inserted);
|
||||
const packedMessage = await this.chatEntityService.packMessageLite(inserted);
|
||||
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.set(`newChatMessageExists:${toUser.id}:${fromUser.id}`, message.id);
|
||||
redisPipeline.set(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`, message.id);
|
||||
redisPipeline.sadd(`newChatMessagesExists:${toUser.id}`, `user:${fromUser.id}`);
|
||||
redisPipeline.exec();
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser)) {
|
||||
// 自分のストリーム
|
||||
this.globalEventService.publishChatStream(fromUser.id, toUser.id, 'message', packedMessage);
|
||||
this.globalEventService.publishChatUserStream(fromUser.id, toUser.id, 'message', packedMessage);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
// 相手のストリーム
|
||||
this.globalEventService.publishChatStream(toUser.id, fromUser.id, 'message', packedMessage);
|
||||
this.globalEventService.publishChatUserStream(toUser.id, fromUser.id, 'message', packedMessage);
|
||||
}
|
||||
|
||||
// 3秒経っても既読にならなかったらイベント発行
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
setTimeout(async () => {
|
||||
const marker = await this.redisClient.get(`newChatMessageExists:${toUser.id}:${fromUser.id}`);
|
||||
const marker = await this.redisClient.get(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`);
|
||||
|
||||
if (marker == null) return; // 既読
|
||||
|
||||
const packedMessageForTo = await this.chatMessageEntityService.pack(inserted, toUser);
|
||||
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
|
||||
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/* TODO: AP
|
||||
if (toUser && this.userEntityService.isLocalUser(fromUser) && this.userEntityService.isRemoteUser(toUser)) {
|
||||
const note = {
|
||||
id: message.id,
|
||||
createdAt: message.createdAt,
|
||||
fileIds: message.fileId ? [message.fileId] : [],
|
||||
text: message.text,
|
||||
userId: message.userId,
|
||||
visibility: 'specified',
|
||||
mentions: [toUser].map(u => u.id),
|
||||
mentionedRemoteUsers: JSON.stringify([toUser].map(u => ({
|
||||
uri: u.uri,
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
}))),
|
||||
} as MiNote;
|
||||
|
||||
const activity = this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false, true), note));
|
||||
|
||||
this.queueService.deliver(fromUser, activity, toUser.inbox);
|
||||
}
|
||||
*/
|
||||
|
||||
return packedMessage;
|
||||
}
|
||||
|
||||
|
@ -203,6 +184,12 @@ export class ChatService {
|
|||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}) {
|
||||
const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id });
|
||||
|
||||
if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) {
|
||||
throw new Error('you are not a member of the room');
|
||||
}
|
||||
|
||||
const message = {
|
||||
id: this.idService.gen(),
|
||||
fromUserId: fromUser.id,
|
||||
|
@ -215,30 +202,36 @@ export class ChatService {
|
|||
|
||||
const inserted = await this.chatMessagesRepository.insertOne(message);
|
||||
|
||||
const packedMessage = await this.chatMessageEntityService.packLite(inserted);
|
||||
const packedMessage = await this.chatEntityService.packMessageLiteForRoom(inserted);
|
||||
|
||||
/*
|
||||
// グループのストリーム
|
||||
this.globalEventService.publishRoomChatStream(toRoom.id, 'message', messageObj);
|
||||
this.globalEventService.publishChatRoomStream(toRoom.id, 'message', packedMessage);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await this.userRoomJoiningsRepository.findBy({ userRoomId: toRoom.id });
|
||||
for (const joining of joinings) {
|
||||
this.globalEventService.publishChatIndexStream(joining.userId, 'message', messageObj);
|
||||
this.globalEventService.publishMainStream(joining.userId, 'chatMessage', messageObj);
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
for (const membership of memberships) {
|
||||
redisPipeline.set(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`, message.id);
|
||||
redisPipeline.sadd(`newChatMessagesExists:${membership.userId}`, `room:${toRoom.id}`);
|
||||
}
|
||||
*/
|
||||
redisPipeline.exec();
|
||||
|
||||
// 3秒経っても既読にならなかったらイベント発行
|
||||
setTimeout(async () => {
|
||||
/*
|
||||
const joinings = await this.userRoomJoiningsRepository.findBy({ userRoomId: toRoom.id, userId: Not(fromUser.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
this.globalEventService.publishMainStream(joining.userId, 'newChatMessage', messageObj);
|
||||
this.pushNotificationService.pushNotification(joining.userId, 'newChatMessage', messageObj);
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
for (const membership of memberships) {
|
||||
redisPipeline.get(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`);
|
||||
}
|
||||
const markers = await redisPipeline.exec();
|
||||
|
||||
if (markers.every(marker => marker[1] == null)) return;
|
||||
|
||||
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted);
|
||||
|
||||
for (let i = 0; i < memberships.length; i++) {
|
||||
const marker = markers[i][1];
|
||||
if (marker == null) continue;
|
||||
|
||||
this.globalEventService.publishMainStream(memberships[i].userId, 'newChatMessage', packedMessageForTo);
|
||||
this.pushNotificationService.pushNotification(memberships[i].userId, 'newChatMessage', packedMessageForTo);
|
||||
}
|
||||
*/
|
||||
}, 3000);
|
||||
|
||||
return packedMessage;
|
||||
|
@ -250,11 +243,22 @@ export class ChatService {
|
|||
senderId: MiUser['id'],
|
||||
): Promise<void> {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.del(`newChatMessageExists:${readerId}:${senderId}`);
|
||||
redisPipeline.del(`newUserChatMessageExists:${readerId}:${senderId}`);
|
||||
redisPipeline.srem(`newChatMessagesExists:${readerId}`, `user:${senderId}`);
|
||||
await redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async readRoomChatMessage(
|
||||
readerId: MiUser['id'],
|
||||
roomId: MiChatRoom['id'],
|
||||
): Promise<void> {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.del(`newRoomChatMessageExists:${readerId}:${roomId}`);
|
||||
redisPipeline.srem(`newChatMessagesExists:${readerId}`, `room:${roomId}`);
|
||||
await redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public findMessageById(messageId: MiChatMessage['id']) {
|
||||
return this.chatMessagesRepository.findOneBy({ id: messageId });
|
||||
|
@ -275,84 +279,17 @@ export class ChatService {
|
|||
this.usersRepository.findOneByOrFail({ id: message.toUserId }),
|
||||
]);
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser)) this.globalEventService.publishChatStream(message.fromUserId, message.toUserId, 'deleted', message.id);
|
||||
if (this.userEntityService.isLocalUser(toUser)) this.globalEventService.publishChatStream(message.toUserId, message.fromUserId, 'deleted', message.id);
|
||||
if (this.userEntityService.isLocalUser(fromUser)) this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId, 'deleted', message.id);
|
||||
if (this.userEntityService.isLocalUser(toUser)) this.globalEventService.publishChatUserStream(message.toUserId, message.fromUserId, 'deleted', message.id);
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser) && this.userEntityService.isRemoteUser(toUser)) {
|
||||
//const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), fromUser));
|
||||
//this.queueService.deliver(fromUser, activity, toUser.inbox);
|
||||
}
|
||||
}/* else if (message.toRoomId) {
|
||||
this.globalEventService.publishRoomChatStream(message.toRoomId, 'deleted', message.id);
|
||||
}*/
|
||||
}
|
||||
|
||||
/*
|
||||
@bindThis
|
||||
public async readRoomChatMessage(
|
||||
userId: MiUser['id'],
|
||||
roomId: MiUserRoom['id'],
|
||||
messageIds: MiChatMessage['id'][],
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
// check joined
|
||||
const joining = await this.userRoomJoiningsRepository.findOneBy({
|
||||
userId: userId,
|
||||
userRoomId: roomId,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (room).');
|
||||
}
|
||||
|
||||
const messages = await this.chatMessagesRepository.findBy({
|
||||
id: In(messageIds),
|
||||
});
|
||||
|
||||
const reads: ChatMessage['id'][] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.userId === userId) continue;
|
||||
if (message.reads.includes(userId)) continue;
|
||||
|
||||
// Update document
|
||||
await this.chatMessagesRepository.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.publishRoomChatStream(roomId, 'read', {
|
||||
ids: reads,
|
||||
userId: userId,
|
||||
});
|
||||
this.globalEventService.publishChatIndexStream(userId, 'read', reads);
|
||||
|
||||
if (!await this.userEntityService.getHasUnreadChatMessage(userId)) {
|
||||
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
|
||||
this.globalEventService.publishMainStream(userId, 'readAllChatMessages');
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllChatMessages', undefined);
|
||||
} else {
|
||||
// そのグループにおいて未読がなければイベント発行
|
||||
const unreadExist = await this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.where('message.toRoomId = :roomId', { roomId: roomId })
|
||||
.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, 'readAllChatMessagesOfARoom', { roomId });
|
||||
} else if (message.toRoomId) {
|
||||
this.globalEventService.publishChatRoomStream(message.toRoomId, 'deleted', message.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@bindThis
|
||||
public async userTimeline(meId: MiUser['id'], otherId: MiUser['id'], sinceId: MiChatMessage['id'] | null, untilId: MiChatMessage['id'] | null, limit: number) {
|
||||
|
@ -421,24 +358,30 @@ export class ChatService {
|
|||
|
||||
@bindThis
|
||||
public async roomHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
|
||||
return [];
|
||||
/*
|
||||
const rooms = await this.userRoomJoiningsRepository.findBy({
|
||||
// TODO: 一回のクエリにまとめられるかも
|
||||
const [memberRoomIds, ownedRoomIds] = await Promise.all([
|
||||
this.chatRoomMembershipsRepository.findBy({
|
||||
userId: meId,
|
||||
}).then(xs => xs.map(x => x.userRoomId));
|
||||
}).then(xs => xs.map(x => x.roomId)),
|
||||
this.chatRoomsRepository.findBy({
|
||||
ownerId: meId,
|
||||
}).then(xs => xs.map(x => x.id)),
|
||||
]);
|
||||
|
||||
if (rooms.length === 0) {
|
||||
const roomIds = memberRoomIds.concat(ownedRoomIds);
|
||||
|
||||
if (memberRoomIds.length === 0 && ownedRoomIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const history: MiChatMessage[] = [];
|
||||
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const found = history.map(m => m.roomId!);
|
||||
const found = history.map(m => m.toRoomId!);
|
||||
|
||||
const query = this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.orderBy('message.id', 'DESC')
|
||||
.where('message.toRoomId IN (:...rooms)', { rooms: rooms });
|
||||
.where('message.toRoomId IN (:...roomIds)', { roomIds });
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere('message.toRoomId NOT IN (:...found)', { found: found });
|
||||
|
@ -454,7 +397,6 @@ export class ChatService {
|
|||
}
|
||||
|
||||
return history;
|
||||
*/
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -464,7 +406,7 @@ export class ChatService {
|
|||
const redisPipeline = this.redisClient.pipeline();
|
||||
|
||||
for (const otherId of otherIds) {
|
||||
redisPipeline.get(`newChatMessageExists:${userId}:${otherId}`);
|
||||
redisPipeline.get(`newUserChatMessageExists:${userId}:${otherId}`);
|
||||
}
|
||||
|
||||
const markers = await redisPipeline.exec();
|
||||
|
@ -477,9 +419,68 @@ export class ChatService {
|
|||
return readStateMap;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRoomReadStateMap(userId: MiUser['id'], roomIds: MiChatRoom['id'][]) {
|
||||
const readStateMap: Record<MiChatRoom['id'], boolean> = {};
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
|
||||
for (const roomId of roomIds) {
|
||||
redisPipeline.get(`newRoomChatMessageExists:${userId}:${roomId}`);
|
||||
}
|
||||
|
||||
const markers = await redisPipeline.exec();
|
||||
|
||||
for (let i = 0; i < roomIds.length; i++) {
|
||||
const marker = markers[i][1];
|
||||
readStateMap[roomIds[i]] = marker == null;
|
||||
}
|
||||
|
||||
return readStateMap;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async hasUnreadMessages(userId: MiUser['id']) {
|
||||
const card = await this.redisClient.scard(`newChatMessagesExists:${userId}`);
|
||||
return card > 0;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createRoom(owner: MiUser, name: string) {
|
||||
const room = {
|
||||
id: this.idService.gen(),
|
||||
name: name,
|
||||
ownerId: owner.id,
|
||||
} satisfies Partial<MiChatRoom>;
|
||||
|
||||
const created = await this.chatRoomsRepository.insertOne(room);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteRoom(room: MiChatRoom) {
|
||||
await this.chatRoomsRepository.delete(room.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createRoomInvitation(inviterId: MiUser['id'], roomId: MiChatRoom['id'], inviteeId: MiUser['id']) {
|
||||
if (inviterId === inviteeId) {
|
||||
throw new Error('yourself');
|
||||
}
|
||||
|
||||
const room = await this.chatRoomsRepository.findOneByOrFail({ id: roomId, ownerId: inviterId });
|
||||
|
||||
// TODO: cehck block
|
||||
|
||||
const invitation = {
|
||||
id: this.idService.gen(),
|
||||
roomId: room.id,
|
||||
userId: inviteeId,
|
||||
} satisfies Partial<MiChatRoomInvitation>;
|
||||
|
||||
const created = await this.chatRoomInvitationsRepository.insertOne(invitation);
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ import { AppEntityService } from './entities/AppEntityService.js';
|
|||
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
||||
import { BlockingEntityService } from './entities/BlockingEntityService.js';
|
||||
import { ChannelEntityService } from './entities/ChannelEntityService.js';
|
||||
import { ChatMessageEntityService } from './entities/ChatMessageEntityService.js';
|
||||
import { ChatEntityService } from './entities/ChatEntityService.js';
|
||||
import { ClipEntityService } from './entities/ClipEntityService.js';
|
||||
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
||||
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
||||
|
@ -248,7 +248,7 @@ const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting:
|
|||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
|
||||
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
|
||||
const $ChatMessageEntityService: Provider = { provide: 'ChatMessageEntityService', useExisting: ChatMessageEntityService };
|
||||
const $ChatEntityService: Provider = { provide: 'ChatEntityService', useExisting: ChatEntityService };
|
||||
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
|
||||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
||||
|
@ -398,7 +398,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
AuthSessionEntityService,
|
||||
BlockingEntityService,
|
||||
ChannelEntityService,
|
||||
ChatMessageEntityService,
|
||||
ChatEntityService,
|
||||
ClipEntityService,
|
||||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
|
@ -544,7 +544,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$AuthSessionEntityService,
|
||||
$BlockingEntityService,
|
||||
$ChannelEntityService,
|
||||
$ChatMessageEntityService,
|
||||
$ChatEntityService,
|
||||
$ClipEntityService,
|
||||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
|
@ -690,7 +690,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
AuthSessionEntityService,
|
||||
BlockingEntityService,
|
||||
ChannelEntityService,
|
||||
ChatMessageEntityService,
|
||||
ChatEntityService,
|
||||
ClipEntityService,
|
||||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
|
@ -834,7 +834,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$AuthSessionEntityService,
|
||||
$BlockingEntityService,
|
||||
$ChannelEntityService,
|
||||
$ChatMessageEntityService,
|
||||
$ChatEntityService,
|
||||
$ClipEntityService,
|
||||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { MiPage } from '@/models/Page.js';
|
|||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import { MiAvatarDecoration, MiChatRoom, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -297,7 +297,11 @@ export type GlobalEvents = {
|
|||
payload: Serialized<Packed<'Note'>>;
|
||||
};
|
||||
chat: {
|
||||
name: `chatStream:${MiUser['id']}-${MiUser['id']}`;
|
||||
name: `chatUserStream:${MiUser['id']}-${MiUser['id']}`;
|
||||
payload: EventTypesToEventPayload<ChatEventTypes>;
|
||||
};
|
||||
chatRoom: {
|
||||
name: `chatRoomStream:${MiChatRoom['id']}`;
|
||||
payload: EventTypesToEventPayload<ChatEventTypes>;
|
||||
};
|
||||
reversi: {
|
||||
|
@ -399,8 +403,13 @@ export class GlobalEventService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public publishChatStream<K extends keyof ChatEventTypes>(fromUserId: MiUser['id'], toUserId: MiUser['id'], type: K, value?: ChatEventTypes[K]): void {
|
||||
this.publish(`chatStream:${fromUserId}-${toUserId}`, type, typeof value === 'undefined' ? null : value);
|
||||
public publishChatUserStream<K extends keyof ChatEventTypes>(fromUserId: MiUser['id'], toUserId: MiUser['id'], type: K, value?: ChatEventTypes[K]): void {
|
||||
this.publish(`chatUserStream:${fromUserId}-${toUserId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishChatRoomStream<K extends keyof ChatEventTypes>(toRoomId: MiChatRoom['id'], type: K, value?: ChatEventTypes[K]): void {
|
||||
this.publish(`chatRoomStream:${toRoomId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser, ChatMessagesRepository, MiChatMessage, ChatRoomsRepository, MiChatRoom } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { } from '@/models/Blocking.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChatEntityService {
|
||||
constructor(
|
||||
@Inject(DI.chatMessagesRepository)
|
||||
private chatMessagesRepository: ChatMessagesRepository,
|
||||
|
||||
@Inject(DI.chatRoomsRepository)
|
||||
private chatRoomsRepository: ChatRoomsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageDetailed(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
me?: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
packedUsers: Map<MiChatMessage['id'], Packed<'UserLite'>>;
|
||||
packedRooms: Map<MiChatMessage['toRoomId'], Packed<'ChatRoom'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessage'>> {
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
const packedRooms = options?._hint_?.packedRooms;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId, me),
|
||||
toUserId: message.toUserId,
|
||||
toUser: message.toUserId ? (packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId, me)) : undefined,
|
||||
toRoomId: message.toRoomId,
|
||||
toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined,
|
||||
fileId: message.fileId,
|
||||
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesDetailed(
|
||||
messages: MiChatMessage[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const excludeMe = (x: MiUser | string) => {
|
||||
if (typeof x === 'string') {
|
||||
return x !== me.id;
|
||||
} else {
|
||||
return x.id !== me.id;
|
||||
}
|
||||
};
|
||||
|
||||
const users = [
|
||||
...messages.map((m) => m.fromUser ?? m.fromUserId).filter(excludeMe),
|
||||
...messages.map((m) => m.toUser ?? m.toUserId).filter(x => x != null).filter(excludeMe),
|
||||
];
|
||||
|
||||
const [packedUsers, packedFiles] = await Promise.all([
|
||||
this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageDetailed(message, me, { _hint_: { packedUsers, packedFiles } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageLite(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
toUserId: message.toUserId,
|
||||
fileId: message.fileId,
|
||||
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesLite(
|
||||
messages: MiChatMessage[],
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const [packedFiles] = await Promise.all([
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageLite(message, { _hint_: { packedFiles } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageLiteForRoom(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
packedUsers: Map<MiChatMessage['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
toUserId: message.toUserId,
|
||||
toUser: packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId),
|
||||
toRoomId: message.toRoomId,
|
||||
fileId: message.fileId,
|
||||
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesLiteForRoom(
|
||||
messages: MiChatMessage[],
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const [packedUsers, packedFiles] = await Promise.all([
|
||||
this.userEntityService.packMany(messages.map(x => x.fromUser))
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageLiteForRoom(message, { _hint_: { packedFiles, packedUsers } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoom(
|
||||
src: MiChatRoom['id'] | MiChatRoom,
|
||||
me?: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedOwners: Map<MiChatRoom['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatRoom'>> {
|
||||
const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: room.id,
|
||||
createdAt: this.idService.parse(room.id).date.toISOString(),
|
||||
name: room.name,
|
||||
ownerId: room.ownerId,
|
||||
owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me),
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRooms(
|
||||
rooms: MiChatRoom[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (rooms.length === 0) return [];
|
||||
|
||||
const owners = rooms.map(x => x.owner ?? x.ownerId);
|
||||
|
||||
const packedOwners = await this.userEntityService.packMany(owners, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
|
||||
return Promise.all(rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners } })));
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser, ChatMessagesRepository, MiChatMessage } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { } from '@/models/Blocking.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChatMessageEntityService {
|
||||
constructor(
|
||||
@Inject(DI.chatMessagesRepository)
|
||||
private chatMessagesRepository: ChatMessagesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
me: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
packedUsers: Map<MiChatMessage['id'], Packed<'UserLite'>>
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessage'>> {
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
fromUser: message.fromUserId !== me.id ? (packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId, me)) : undefined,
|
||||
toUserId: message.toUserId,
|
||||
toUser: (message.toUserId && message.toUserId !== me.id) ? (packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId, me)) : undefined,
|
||||
//toRoomId: message.toRoomId,
|
||||
//toRoom: message.toRoomId && opts.populateRoom ? await this.userRoomEntityService.pack(message.toRoom ?? message.toRoomId) : undefined,
|
||||
fileId: message.fileId,
|
||||
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMany(
|
||||
messages: MiChatMessage[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const excludeMe = (x: MiUser | string) => {
|
||||
if (typeof x === 'string') {
|
||||
return x !== me.id;
|
||||
} else {
|
||||
return x.id !== me.id;
|
||||
}
|
||||
};
|
||||
|
||||
const users = [
|
||||
...messages.map((m) => m.fromUser ?? m.fromUserId).filter(excludeMe),
|
||||
...messages.map((m) => m.toUser ?? m.toUserId).filter(x => x != null).filter(excludeMe),
|
||||
];
|
||||
|
||||
const [packedUsers, packedFiles] = await Promise.all([
|
||||
this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.pack(message, me, { _hint_: { packedUsers, packedFiles } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packLite(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
toUserId: message.toUserId,
|
||||
//toRoomId: message.toRoomId,
|
||||
fileId: message.fileId,
|
||||
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packLiteMany(
|
||||
messages: MiChatMessage[],
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const [packedFiles] = await Promise.all([
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packLite(message, { _hint_: { packedFiles } })));
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,7 @@ export const DI = {
|
|||
chatApprovalsRepository: Symbol('chatApprovalsRepository'),
|
||||
chatRoomsRepository: Symbol('chatRoomsRepository'),
|
||||
chatRoomMembershipsRepository: Symbol('chatRoomMembershipsRepository'),
|
||||
chatRoomInvitationsRepository: Symbol('chatRoomInvitationsRepository'),
|
||||
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
|
||||
reversiGamesRepository: Symbol('reversiGamesRepository'),
|
||||
//#endregion
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_room_invitation')
|
||||
@Index(['userId', 'roomId'], { unique: true })
|
||||
export class MiChatRoomInvitation {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public room: MiChatRoom | null;
|
||||
}
|
|
@ -80,6 +80,7 @@ import {
|
|||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
} from './_.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
@ -505,6 +506,12 @@ const $chatRoomMembershipsRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatRoomInvitationsRepository: Provider = {
|
||||
provide: DI.chatRoomInvitationsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatRoomInvitation).extend(miRepository as MiRepository<MiChatRoomInvitation>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatApprovalsRepository: Provider = {
|
||||
provide: DI.chatApprovalsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatApproval).extend(miRepository as MiRepository<MiChatApproval>),
|
||||
|
@ -596,6 +603,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$chatMessagesRepository,
|
||||
$chatRoomsRepository,
|
||||
$chatRoomMembershipsRepository,
|
||||
$chatRoomInvitationsRepository,
|
||||
$chatApprovalsRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
|
@ -671,6 +679,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$chatMessagesRepository,
|
||||
$chatRoomsRepository,
|
||||
$chatRoomMembershipsRepository,
|
||||
$chatRoomInvitationsRepository,
|
||||
$chatApprovalsRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
|
|
|
@ -77,6 +77,7 @@ import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
|||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
import { MiChatApproval } from '@/models/ChatApproval.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
|
@ -196,6 +197,7 @@ export {
|
|||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
|
@ -271,6 +273,7 @@ export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMem
|
|||
export type ChatMessagesRepository = Repository<MiChatMessage> & MiRepository<MiChatMessage>;
|
||||
export type ChatRoomsRepository = Repository<MiChatRoom> & MiRepository<MiChatRoom>;
|
||||
export type ChatRoomMembershipsRepository = Repository<MiChatRoomMembership> & MiRepository<MiChatRoomMembership>;
|
||||
export type ChatRoomInvitationsRepository = Repository<MiChatRoomInvitation> & MiRepository<MiChatRoomInvitation>;
|
||||
export type ChatApprovalsRepository = Repository<MiChatApproval> & MiRepository<MiChatApproval>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
|
||||
|
|
|
@ -81,6 +81,7 @@ import { MiUserMemo } from '@/models/UserMemo.js';
|
|||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
import { MiChatApproval } from '@/models/ChatApproval.js';
|
||||
|
@ -240,6 +241,7 @@ export const entities = [
|
|||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
|
|
|
@ -44,7 +44,8 @@ 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';
|
||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||
import { ChatChannelService } from './api/stream/channels/chat.js';
|
||||
import { ChatUserChannelService } from './api/stream/channels/chat-user.js';
|
||||
import { ChatRoomChannelService } from './api/stream/channels/chat-room.js';
|
||||
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
||||
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
|
||||
|
@ -85,7 +86,8 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
|||
GlobalTimelineChannelService,
|
||||
HashtagChannelService,
|
||||
RoleTimelineChannelService,
|
||||
ChatChannelService,
|
||||
ChatUserChannelService,
|
||||
ChatRoomChannelService,
|
||||
ReversiChannelService,
|
||||
ReversiGameChannelService,
|
||||
HomeTimelineChannelService,
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatMessageEntityService } from '@/core/entities/ChatMessageEntityService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -42,15 +42,21 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatMessageEntityService: ChatMessageEntityService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const history = ps.room ? await this.chatService.roomHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit);
|
||||
|
||||
const packedMessages = await this.chatMessageEntityService.packMany(history, me);
|
||||
const packedMessages = await this.chatEntityService.packMessagesDetailed(history, me);
|
||||
|
||||
if (ps.room) {
|
||||
const roomIds = history.map(m => m.toRoomId!);
|
||||
const readStateMap = await this.chatService.getRoomReadStateMap(me.id, roomIds);
|
||||
|
||||
for (const message of packedMessages) {
|
||||
message.isRead = readStateMap[message.toRoomId!] ?? false;
|
||||
}
|
||||
} else {
|
||||
const otherIds = history.map(m => m.fromUserId === me.id ? m.toUserId! : m.fromUserId!);
|
||||
const readStateMap = await this.chatService.getUserReadStateMap(me.id, otherIds);
|
||||
|
|
|
@ -125,7 +125,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw err;
|
||||
});
|
||||
|
||||
return await this.chatService.createMessage(me, toUser, {
|
||||
return await this.chatService.createMessageToUser(me, toUser, {
|
||||
text: ps.text,
|
||||
file: file,
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ 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 { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
|
@ -8,7 +8,7 @@ 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 { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
|
@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
private chatService: ChatService,
|
||||
private roleService: RoleService,
|
||||
private chatMessageEntityService: ChatMessageEntityService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const message = await this.chatService.findMessageById(ps.messageId);
|
||||
|
@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
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);
|
||||
return this.chatEntityService.packMessageDetailed(message, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ 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 { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -62,7 +62,7 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatMessageEntityService: ChatMessageEntityService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
|
@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
this.chatService.readUserChatMessage(me.id, other.id);
|
||||
|
||||
return await this.chatMessageEntityService.packLiteMany(messages);
|
||||
return await this.chatEntityService.packMessagesLite(messages);
|
||||
}/* else if (ps.roomId != null) {
|
||||
// Fetch recipient (room)
|
||||
const recipientRoom = await this.userRoomRepository.findOneBy({ id: ps.roomId });
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
limit: {
|
||||
duration: ms('1day'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', maxLength: 256 },
|
||||
},
|
||||
required: ['name'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.createRoom(me, ps.name);
|
||||
return await this.chatEntityService.packRoom(room);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ import { AntennaChannelService } from './channels/antenna.js';
|
|||
import { DriveChannelService } from './channels/drive.js';
|
||||
import { HashtagChannelService } from './channels/hashtag.js';
|
||||
import { RoleTimelineChannelService } from './channels/role-timeline.js';
|
||||
import { ChatChannelService } from './channels/chat.js';
|
||||
import { ChatUserChannelService } from './channels/chat-user.js';
|
||||
import { ChatRoomChannelService } from './channels/chat-room.js';
|
||||
import { ReversiChannelService } from './channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './channels/reversi-game.js';
|
||||
import { type MiChannelService } from './channel.js';
|
||||
|
@ -41,7 +42,8 @@ export class ChannelsService {
|
|||
private serverStatsChannelService: ServerStatsChannelService,
|
||||
private queueStatsChannelService: QueueStatsChannelService,
|
||||
private adminChannelService: AdminChannelService,
|
||||
private chatChannelService: ChatChannelService,
|
||||
private chatUserChannelService: ChatUserChannelService,
|
||||
private chatRoomChannelService: ChatRoomChannelService,
|
||||
private reversiChannelService: ReversiChannelService,
|
||||
private reversiGameChannelService: ReversiGameChannelService,
|
||||
) {
|
||||
|
@ -64,7 +66,8 @@ export class ChannelsService {
|
|||
case 'serverStats': return this.serverStatsChannelService;
|
||||
case 'queueStats': return this.queueStatsChannelService;
|
||||
case 'admin': return this.adminChannelService;
|
||||
case 'chat': return this.chatChannelService;
|
||||
case 'chatUser': return this.chatUserChannelService;
|
||||
case 'chatRoom': return this.chatRoomChannelService;
|
||||
case 'reversi': return this.reversiChannelService;
|
||||
case 'reversiGame': return this.reversiGameChannelService;
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import Channel, { type MiChannelService } from '../channel.js';
|
||||
|
||||
class ChatRoomChannel extends Channel {
|
||||
public readonly chName = 'chatRoom';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true as const;
|
||||
public static kind = 'read:chat';
|
||||
private roomId: string;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
|
||||
id: string,
|
||||
connection: Channel['connection'],
|
||||
) {
|
||||
super(id, connection);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.roomId !== 'string') return;
|
||||
this.roomId = params.roomId;
|
||||
|
||||
this.subscriber.on(`chatRoomStream:${this.roomId}`, this.onEvent);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onEvent(data: GlobalEvents['chat']['payload']) {
|
||||
this.send(data.type, data.body);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onMessage(type: string, body: any) {
|
||||
switch (type) {
|
||||
case 'read':
|
||||
if (this.roomId) {
|
||||
this.chatService.readRoomChatMessage(this.user!.id, this.roomId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose() {
|
||||
this.subscriber.off(`chatRoomStream:${this.roomId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChatRoomChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = ChatRoomChannel.shouldShare;
|
||||
public readonly requireCredential = ChatRoomChannel.requireCredential;
|
||||
public readonly kind = ChatRoomChannel.kind;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(id: string, connection: Channel['connection']): ChatRoomChannel {
|
||||
return new ChatRoomChannel(
|
||||
this.chatService,
|
||||
id,
|
||||
connection,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,8 +10,8 @@ import type { JsonObject } from '@/misc/json-value.js';
|
|||
import { ChatService } from '@/core/ChatService.js';
|
||||
import Channel, { type MiChannelService } from '../channel.js';
|
||||
|
||||
class ChatChannel extends Channel {
|
||||
public readonly chName = 'chat';
|
||||
class ChatUserChannel extends Channel {
|
||||
public readonly chName = 'chatUser';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true as const;
|
||||
public static kind = 'read:chat';
|
||||
|
@ -31,7 +31,7 @@ class ChatChannel extends Channel {
|
|||
if (typeof params.otherId !== 'string') return;
|
||||
this.otherId = params.otherId;
|
||||
|
||||
this.subscriber.on(`chatStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
this.subscriber.on(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -52,16 +52,15 @@ class ChatChannel extends Channel {
|
|||
|
||||
@bindThis
|
||||
public dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off(`chatStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
this.subscriber.off(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChatChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = ChatChannel.shouldShare;
|
||||
public readonly requireCredential = ChatChannel.requireCredential;
|
||||
public readonly kind = ChatChannel.kind;
|
||||
export class ChatUserChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = ChatUserChannel.shouldShare;
|
||||
public readonly requireCredential = ChatUserChannel.requireCredential;
|
||||
public readonly kind = ChatUserChannel.kind;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
|
@ -69,8 +68,8 @@ export class ChatChannelService implements MiChannelService<true> {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public create(id: string, connection: Channel['connection']): ChatChannel {
|
||||
return new ChatChannel(
|
||||
public create(id: string, connection: Channel['connection']): ChatUserChannel {
|
||||
return new ChatUserChannel(
|
||||
this.chatService,
|
||||
id,
|
||||
connection,
|
|
@ -88,11 +88,11 @@ const connection = ref<Misskey.ChannelConnection<Misskey.Channels['chat']> | nul
|
|||
const showIndicator = ref(false);
|
||||
|
||||
async function initialize() {
|
||||
const LIMIT = 20;
|
||||
|
||||
initializing.value = true;
|
||||
|
||||
if (props.userId) {
|
||||
const LIMIT = 20;
|
||||
|
||||
const [u, m] = await Promise.all([
|
||||
misskeyApi('users/show', { userId: props.userId }),
|
||||
misskeyApi('chat/messages/timeline', { userId: props.userId, limit: LIMIT }),
|
||||
|
@ -105,28 +105,30 @@ async function initialize() {
|
|||
canFetchMore.value = true;
|
||||
}
|
||||
|
||||
connection.value = useStream().useChannel('chat', {
|
||||
connection.value = useStream().useChannel('chatUser', {
|
||||
otherId: user.value.id,
|
||||
});
|
||||
}/* else {
|
||||
user = null;
|
||||
room = await misskeyApi('users/rooms/show', { roomId: props.roomId });
|
||||
|
||||
pagination = {
|
||||
endpoint: 'chat/messages',
|
||||
limit: 20,
|
||||
params: {
|
||||
roomId: room?.id,
|
||||
},
|
||||
reversed: true,
|
||||
};
|
||||
connection = useStream().useChannel('chat', {
|
||||
room: room?.id,
|
||||
});
|
||||
}*/
|
||||
|
||||
connection.value.on('message', onMessage);
|
||||
connection.value.on('deleted', onDeleted);
|
||||
} else {
|
||||
const [r, m] = await Promise.all([
|
||||
misskeyApi('chat/rooms/show', { roomId: props.roomId }),
|
||||
misskeyApi('chat/messages/timeline', { roomId: props.roomId, limit: LIMIT }),
|
||||
]);
|
||||
|
||||
room.value = r;
|
||||
messages.value = m;
|
||||
|
||||
if (messages.value.length === LIMIT) {
|
||||
canFetchMore.value = true;
|
||||
}
|
||||
|
||||
connection.value = useStream().useChannel('chatRoom', {
|
||||
otherId: user.value.id,
|
||||
});
|
||||
connection.value.on('message', onMessage);
|
||||
connection.value.on('deleted', onDeleted);
|
||||
}
|
||||
|
||||
window.document.addEventListener('visibilitychange', onVisibilitychange);
|
||||
|
||||
|
|
|
@ -971,6 +971,12 @@ type ChatMessagesDeleteRequest = operations['chat___messages___delete']['request
|
|||
// @public (undocumented)
|
||||
type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesTimelineRequest = operations['chat___messages___timeline']['requestBody']['content']['application/json'];
|
||||
|
||||
|
@ -1483,6 +1489,8 @@ declare namespace entities {
|
|||
ChatMessagesCreateResponse,
|
||||
ChatMessagesDeleteRequest,
|
||||
ChatMessagesDeleteResponse,
|
||||
ChatMessagesShowRequest,
|
||||
ChatMessagesShowResponse,
|
||||
ChatMessagesTimelineRequest,
|
||||
ChatMessagesTimelineResponse,
|
||||
ClipsAddNoteRequest,
|
||||
|
|
|
@ -1567,6 +1567,17 @@ declare module '../api.js' {
|
|||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
request<E extends 'chat/messages/show', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
|
|
@ -213,6 +213,8 @@ import type {
|
|||
ChatMessagesCreateResponse,
|
||||
ChatMessagesDeleteRequest,
|
||||
ChatMessagesDeleteResponse,
|
||||
ChatMessagesShowRequest,
|
||||
ChatMessagesShowResponse,
|
||||
ChatMessagesTimelineRequest,
|
||||
ChatMessagesTimelineResponse,
|
||||
ClipsAddNoteRequest,
|
||||
|
@ -737,6 +739,7 @@ export type Endpoints = {
|
|||
'chat/history': { req: ChatHistoryRequest; res: ChatHistoryResponse };
|
||||
'chat/messages/create': { req: ChatMessagesCreateRequest; res: ChatMessagesCreateResponse };
|
||||
'chat/messages/delete': { req: ChatMessagesDeleteRequest; res: ChatMessagesDeleteResponse };
|
||||
'chat/messages/show': { req: ChatMessagesShowRequest; res: ChatMessagesShowResponse };
|
||||
'chat/messages/timeline': { req: ChatMessagesTimelineRequest; res: ChatMessagesTimelineResponse };
|
||||
'clips/add-note': { req: ClipsAddNoteRequest; res: EmptyResponse };
|
||||
'clips/create': { req: ClipsCreateRequest; res: ClipsCreateResponse };
|
||||
|
|
|
@ -216,6 +216,8 @@ export type ChatMessagesCreateRequest = operations['chat___messages___create']['
|
|||
export type ChatMessagesCreateResponse = operations['chat___messages___create']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesTimelineRequest = operations['chat___messages___timeline']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesTimelineResponse = operations['chat___messages___timeline']['responses']['200']['content']['application/json'];
|
||||
export type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json'];
|
||||
|
|
|
@ -1385,6 +1385,15 @@ export type paths = {
|
|||
*/
|
||||
post: operations['chat___messages___delete'];
|
||||
};
|
||||
'/chat/messages/show': {
|
||||
/**
|
||||
* chat/messages/show
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
post: operations['chat___messages___show'];
|
||||
};
|
||||
'/chat/messages/timeline': {
|
||||
/**
|
||||
* chat/messages/timeline
|
||||
|
@ -13907,6 +13916,60 @@ export type operations = {
|
|||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/show
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
chat___messages___show: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
messageId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['ChatMessage'];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Authentication error */
|
||||
401: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden error */
|
||||
403: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description I'm Ai */
|
||||
418: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/timeline
|
||||
* @description No description provided.
|
||||
|
|
Loading…
Reference in New Issue