diff --git a/locales/index.d.ts b/locales/index.d.ts index cad675a45b..991dc45c04 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5376,6 +5376,54 @@ export interface Locale extends ILocale { * また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。 */ "roomChat_description": string; + /** + * ルームを作成 + */ + "createRoom": string; + /** + * ユーザーを招待してチャットを始めましょう + */ + "inviteUserToChat": string; + /** + * 作成したルーム + */ + "yourRooms": string; + /** + * 参加中のルーム + */ + "joiningRooms": string; + /** + * 招待 + */ + "invitations": string; + /** + * 招待はありません + */ + "noInvitations": string; + /** + * 履歴 + */ + "history": string; + /** + * 履歴はありません + */ + "noHistory": string; + /** + * ルームはありません + */ + "noRooms": string; + /** + * ユーザーを招待 + */ + "inviteUser": string; + /** + * 参加 + */ + "join": string; + /** + * 無視 + */ + "ignore": string; /** * このユーザーとのチャットを開始できません */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d25efe198b..8d304a5910 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1341,6 +1341,18 @@ _chat: individualChat_description: "特定ユーザーとの一対一のチャットができます。" roomChat: "ルームチャット" roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。" + createRoom: "ルームを作成" + inviteUserToChat: "ユーザーを招待してチャットを始めましょう" + yourRooms: "作成したルーム" + joiningRooms: "参加中のルーム" + invitations: "招待" + noInvitations: "招待はありません" + history: "履歴" + noHistory: "履歴はありません" + noRooms: "ルームはありません" + inviteUser: "ユーザーを招待" + join: "参加" + ignore: "無視" cannotChatWithTheUser: "このユーザーとのチャットを開始できません" cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" chatWithThisUser: "チャットする" diff --git a/packages/backend/migration/1742721896936-chat-5.js b/packages/backend/migration/1742721896936-chat-5.js new file mode 100644 index 0000000000..00db787cb7 --- /dev/null +++ b/packages/backend/migration/1742721896936-chat-5.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class Chat51742721896936 { + name = 'Chat51742721896936' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD "ignored" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP COLUMN "ignored"`); + } +} diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 6b09c44baf..0c8907b8dd 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -22,6 +22,7 @@ import { QueryService } from '@/core/QueryService.js'; import { RoleService } from '@/core/RoleService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js'; +import { Packed } from '@/misc/json-schema.js'; const MAX_ROOM_MEMBERS = 30; @@ -74,7 +75,7 @@ export class ChatService { text?: string | null; file?: MiDriveFile | null; uri?: string | null; - }) { + }): Promise> { if (fromUser.id === toUser.id) { throw new Error('yourself'); } @@ -185,7 +186,7 @@ export class ChatService { text?: string | null; file?: MiDriveFile | null; uri?: string | null; - }) { + }): Promise> { const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id }); if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) { @@ -294,7 +295,7 @@ export class ChatService { } @bindThis - public async userTimeline(meId: MiUser['id'], otherId: MiUser['id'], sinceId: MiChatMessage['id'] | null, untilId: MiChatMessage['id'] | null, limit: number) { + public async userTimeline(meId: MiUser['id'], otherId: MiUser['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) { const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId) .andWhere(new Brackets(qb => { qb @@ -317,6 +318,16 @@ export class ChatService { return messages; } + @bindThis + public async roomTimeline(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) { + const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId) + .where('message.toRoomId = :roomId', { roomId }); + + const messages = await query.take(limit).getMany(); + + return messages; + } + @bindThis public async userHistory(meId: MiUser['id'], limit: number): Promise { const history: MiChatMessage[] = []; @@ -525,7 +536,8 @@ export class ChatService { @bindThis public async getReceivedRoomInvitationsWithPagination(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) { const query = this.queryService.makePaginationQuery(this.chatRoomInvitationsRepository.createQueryBuilder('invitation'), sinceId, untilId) - .where('invitation.userId = :userId', { userId }); + .where('invitation.userId = :userId', { userId }) + .andWhere('invitation.ignored = FALSE'); const invitations = await query.take(limit).getMany(); @@ -553,9 +565,9 @@ export class ChatService { } @bindThis - public async rejectRoomInvitation(userId: MiUser['id'], roomId: MiChatRoom['id']) { + public async ignoreRoomInvitation(userId: MiUser['id'], roomId: MiChatRoom['id']) { const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId }); - await this.chatRoomInvitationsRepository.delete(invitation.id); + await this.chatRoomInvitationsRepository.update(invitation.id, { ignored: true }); } @bindThis diff --git a/packages/backend/src/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts index 4fd2ed5702..bb03d229dc 100644 --- a/packages/backend/src/core/entities/ChatEntityService.ts +++ b/packages/backend/src/core/entities/ChatEntityService.ts @@ -154,8 +154,7 @@ export class ChatEntityService { 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), + fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId), toRoomId: message.toRoomId, fileId: message.fileId, file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null, @@ -169,7 +168,7 @@ export class ChatEntityService { if (messages.length === 0) return []; const [packedUsers, packedFiles] = await Promise.all([ - this.userEntityService.packMany(messages.map(x => x.fromUser)) + this.userEntityService.packMany(messages.map(x => x.fromUser ?? x.fromUserId)) .then(users => new Map(users.map(u => [u.id, u]))), this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)), ]); diff --git a/packages/backend/src/models/ChatRoomInvitation.ts b/packages/backend/src/models/ChatRoomInvitation.ts index 1e0c8bd746..36ce12bc92 100644 --- a/packages/backend/src/models/ChatRoomInvitation.ts +++ b/packages/backend/src/models/ChatRoomInvitation.ts @@ -37,4 +37,9 @@ export class MiChatRoomInvitation { }) @JoinColumn() public room: MiChatRoom | null; + + @Column('boolean', { + default: false, + }) + public ignored: boolean; } diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index a1bf63003b..0514119dea 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -396,10 +396,12 @@ export * as 'users/search' from './endpoints/users/search.js'; export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; 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/create-to-user' from './endpoints/chat/messages/create-to-user.js'; +export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.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/user-timeline' from './endpoints/chat/messages/user-timeline.js'; +export * as 'chat/messages/room-timeline' from './endpoints/chat/messages/room-timeline.js'; export * as 'chat/rooms/create' from './endpoints/chat/rooms/create.js'; export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js'; export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.js'; @@ -409,7 +411,7 @@ export * as 'chat/rooms/owned' from './endpoints/chat/rooms/owned.js'; export * as 'chat/rooms/update' from './endpoints/chat/rooms/update.js'; export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.js'; export * as 'chat/rooms/invitations/create' from './endpoints/chat/rooms/invitations/create.js'; -export * as 'chat/rooms/invitations/reject' from './endpoints/chat/rooms/invitations/reject.js'; +export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitations/ignore.js'; export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.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/create-to-room.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts new file mode 100644 index 0000000000..1f334d5750 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts @@ -0,0 +1,105 @@ +/* + * 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 { GetterService } from '@/server/api/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { ChatService } from '@/core/ChatService.js'; +import type { DriveFilesRepository, MiUser } from '@/models/_.js'; + +export const meta = { + tags: ['chat'], + + requireCredential: true, + requiredRolePolicy: 'canChat', + + prohibitMoved: true, + + kind: 'write:chat', + + limit: { + duration: ms('1hour'), + max: 500, + }, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessageLite', + }, + + errors: { + noSuchRoom: { + message: 'No such room.', + code: 'NO_SUCH_ROOM', + id: '8098520d-2da5-4e8f-8ee1-df78b55a4ec6', + }, + + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'b6accbd3-1d7b-4d9f-bdb7-eb185bac06db', + }, + + contentRequired: { + message: 'Content required. You need to set text or fileId.', + code: 'CONTENT_REQUIRED', + id: '340517b7-6d04-42c0-bac1-37ee804e3594', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + text: { type: 'string', nullable: true, maxLength: 2000 }, + fileId: { type: 'string', format: 'misskey:id' }, + toRoomId: { type: 'string', format: 'misskey:id' }, + }, + required: ['toRoomId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + private getterService: GetterService, + private chatService: ChatService, + ) { + super(meta, paramDef, async (ps, me) => { + const room = await this.chatService.findRoomById(ps.toRoomId); + if (room == null) { + throw new ApiError(meta.errors.noSuchRoom); + } + + 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.chatService.createMessageToRoom(me, room, { + text: ps.text, + file: file, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts similarity index 66% rename from packages/backend/src/server/api/endpoints/chat/messages/create.ts rename to packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts index cba8c84dfc..6b77a026fb 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts @@ -46,18 +46,6 @@ export const meta = { id: '11795c64-40ea-4198-b06e-3c873ed9039d', }, - noSuchRoom: { - message: 'No such room.', - code: 'NO_SUCH_ROOM', - id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537', - }, - - roomAccessDenied: { - message: 'You can not send messages to rooms that you have not joined.', - code: 'ROOM_ACCESS_DENIED', - id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd', - }, - noSuchFile: { message: 'No such file.', code: 'NO_SUCH_FILE', @@ -83,8 +71,9 @@ export const paramDef = { properties: { text: { type: 'string', nullable: true, maxLength: 2000 }, fileId: { type: 'string', format: 'misskey:id' }, - toUserId: { type: 'string', format: 'misskey:id', nullable: true }, + toUserId: { type: 'string', format: 'misskey:id' }, }, + required: ['toUserId'], } as const; @Injectable() @@ -114,39 +103,20 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.contentRequired); } - if (ps.toUserId != null) { - // Myself - if (ps.toUserId === me.id) { - throw new ApiError(meta.errors.recipientIsYourself); - } + // Myself + if (ps.toUserId === me.id) { + throw new ApiError(meta.errors.recipientIsYourself); + } - const toUser = await this.getterService.getUser(ps.toUserId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw err; - }); + const toUser = await this.getterService.getUser(ps.toUserId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); - return await this.chatService.createMessageToUser(me, toUser, { - text: ps.text, - file: file, - }); - }/* else if (ps.toRoomId != null) { - // Fetch recipient (room) - recipientRoom = await this.userRoomsRepository.findOneBy({ id: ps.toRoomId! }); - - if (recipientRoom == null) { - throw new ApiError(meta.errors.noSuchRoom); - } - - // check joined - const joining = await this.userRoomJoiningsRepository.findOneBy({ - userId: me.id, - userRoomId: recipientRoom.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.roomAccessDenied); - } - }*/ + return await this.chatService.createMessageToUser(me, toUser, { + text: ps.text, + file: file, + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts new file mode 100644 index 0000000000..1e0af7bc3d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts @@ -0,0 +1,73 @@ +/* + * 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 { ChatService } from '@/core/ChatService.js'; +import { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['chat'], + + requireCredential: true, + + kind: 'read:chat', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessageLite', + }, + }, + + errors: { + noSuchRoom: { + message: 'No such room.', + code: 'NO_SUCH_ROOM', + id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f', + }, + }, +} 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' }, + roomId: { type: 'string', format: 'misskey:id' }, + }, + required: ['roomId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private chatEntityService: ChatEntityService, + private chatService: ChatService, + ) { + super(meta, paramDef, async (ps, me) => { + const room = await this.chatService.findRoomById(ps.roomId); + if (room == null) { + throw new ApiError(meta.errors.noSuchRoom); + } + + if (!(await this.chatService.isRoomMember(room.id, me.id)) && room.ownerId !== me.id) { + throw new ApiError(meta.errors.noSuchRoom); + } + + const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId); + + this.chatService.readRoomChatMessage(me.id, room.id); + + return await this.chatEntityService.packMessagesLiteForRoom(messages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/chat/messages/timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/timeline.ts deleted file mode 100644 index b4a4020a97..0000000000 --- a/packages/backend/src/server/api/endpoints/chat/messages/timeline.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { ChatEntityService } from '@/core/entities/ChatEntityService.js'; -import { ApiError } from '@/server/api/error.js'; - -export const meta = { - tags: ['chat'], - - requireCredential: true, - - kind: 'read:chat', - - res: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - ref: 'ChatMessageLite', - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '11795c64-40ea-4198-b06e-3c873ed9039d', - }, - - noSuchRoom: { - message: 'No such room.', - code: 'NO_SUCH_ROOM', - id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f', - }, - - roomAccessDenied: { - message: 'You can not read messages of rooms that you have not joined.', - code: 'ROOM_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' }, - userId: { type: 'string', format: 'misskey:id', nullable: true }, - }, -} as const; - -@Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export - constructor( - private chatEntityService: ChatEntityService, - private chatService: ChatService, - private getterService: GetterService, - ) { - super(meta, paramDef, async (ps, me) => { - if (ps.userId != null) { - const other = 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 messages = await this.chatService.userTimeline(me.id, other.id, ps.sinceId, ps.untilId, ps.limit); - - this.chatService.readUserChatMessage(me.id, other.id); - - return await this.chatEntityService.packMessagesLite(messages); - }/* else if (ps.roomId != null) { - // Fetch recipient (room) - const recipientRoom = await this.userRoomRepository.findOneBy({ id: ps.roomId }); - - if (recipientRoom == null) { - throw new ApiError(meta.errors.noSuchRoom); - } - - // check joined - const joining = await this.userRoomJoiningsRepository.findOneBy({ - userId: me.id, - userRoomId: recipientRoom.id, - }); - - if (joining == null) { - throw new ApiError(meta.errors.roomAccessDenied); - } - - const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere('message.roomId = :roomId', { roomId: recipientRoom.id }); - - const messages = await query.take(ps.limit).getMany(); - - // Mark all as read - if (ps.markAsRead) { - this.chatService.readRoomMessagingMessage(me.id, recipientRoom.id, messages.map(x => x.id)); - } - - return await Promise.all(messages.map(message => this.chatMessageEntityService.pack(message, me, { - populateRoom: false, - }))); - }*/ - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts new file mode 100644 index 0000000000..835d4000c9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts @@ -0,0 +1,71 @@ +/* + * 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 { ChatEntityService } from '@/core/entities/ChatEntityService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['chat'], + + requireCredential: true, + + kind: 'read:chat', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'ChatMessageLite', + }, + }, + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '11795c64-40ea-4198-b06e-3c873ed9039d', + }, + }, +} 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' }, + userId: { type: 'string', format: 'misskey:id' }, + }, + required: ['userId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private chatEntityService: ChatEntityService, + private chatService: ChatService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + const other = 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 messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId); + + this.chatService.readUserChatMessage(me.id, other.id); + + return await this.chatEntityService.packMessagesLite(messages); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts index 6a1e3f0f73..5da4a1a772 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts @@ -61,7 +61,7 @@ export default class extends Endpoint { // eslint- if (room == null) { throw new ApiError(meta.errors.noSuchRoom); } - const invitation = await this.chatService.createRoomInvitation(ps.userId, room.id, ps.userId); + const invitation = await this.chatService.createRoomInvitation(me.id, room.id, ps.userId); return await this.chatEntityService.packRoomInvitation(invitation, me); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/reject.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts similarity index 94% rename from packages/backend/src/server/api/endpoints/chat/rooms/invitations/reject.ts rename to packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts index 6a83ecc29a..8c017f7d01 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts @@ -42,7 +42,7 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { - await this.chatService.rejectRoomInvitation(me.id, ps.roomId); + await this.chatService.ignoreRoomInvitation(me.id, ps.roomId); }); } } diff --git a/packages/frontend/src/pages/chat/home.history.vue b/packages/frontend/src/pages/chat/home.history.vue new file mode 100644 index 0000000000..904ad378a8 --- /dev/null +++ b/packages/frontend/src/pages/chat/home.history.vue @@ -0,0 +1,204 @@ + + + + + + + diff --git a/packages/frontend/src/pages/chat/home.invitations.vue b/packages/frontend/src/pages/chat/home.invitations.vue new file mode 100644 index 0000000000..c7e7ab3a02 --- /dev/null +++ b/packages/frontend/src/pages/chat/home.invitations.vue @@ -0,0 +1,96 @@ + + + + + + + diff --git a/packages/frontend/src/pages/chat/home.ownedRooms.vue b/packages/frontend/src/pages/chat/home.ownedRooms.vue new file mode 100644 index 0000000000..b6d6cece2f --- /dev/null +++ b/packages/frontend/src/pages/chat/home.ownedRooms.vue @@ -0,0 +1,57 @@ + + + + + + + diff --git a/packages/frontend/src/pages/chat/home.vue b/packages/frontend/src/pages/chat/home.vue index 02990d7d89..e808b6b614 100644 --- a/packages/frontend/src/pages/chat/home.vue +++ b/packages/frontend/src/pages/chat/home.vue @@ -4,139 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/pages/chat/room.form.vue b/packages/frontend/src/pages/chat/room.form.vue index a416336504..8594fc7a15 100644 --- a/packages/frontend/src/pages/chat/room.form.vue +++ b/packages/frontend/src/pages/chat/room.form.vue @@ -168,18 +168,32 @@ function send() { if (!canSend.value) return; sending.value = true; - misskeyApi('chat/messages/create', { - toUserId: props.user ? props.user.id : undefined, - toRoomId: props.room ? props.room.id : undefined, - text: text.value ? text.value : undefined, - fileId: file.value ? file.value.id : undefined, - }).then(message => { - clear(); - }).catch(err => { - console.error(err); - }).then(() => { - sending.value = false; - }); + + if (props.user) { + misskeyApi('chat/messages/create-to-user', { + toUserId: props.user.id, + text: text.value ? text.value : undefined, + fileId: file.value ? file.value.id : undefined, + }).then(message => { + clear(); + }).catch(err => { + console.error(err); + }).then(() => { + sending.value = false; + }); + } else if (props.room) { + misskeyApi('chat/messages/create-to-room', { + toRoomId: props.room.id, + text: text.value ? text.value : undefined, + fileId: file.value ? file.value.id : undefined, + }).then(message => { + clear(); + }).catch(err => { + console.error(err); + }).then(() => { + sending.value = false; + }); + } } function clear() { diff --git a/packages/frontend/src/pages/chat/room.vue b/packages/frontend/src/pages/chat/room.vue index e293ccbf6e..023f831e1b 100644 --- a/packages/frontend/src/pages/chat/room.vue +++ b/packages/frontend/src/pages/chat/room.vue @@ -4,20 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only -->