wip
This commit is contained in:
48
locales/index.d.ts
vendored
48
locales/index.d.ts
vendored
@@ -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;
|
||||
/**
|
||||
* このユーザーとのチャットを開始できません
|
||||
*/
|
||||
|
@@ -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: "チャットする"
|
||||
|
16
packages/backend/migration/1742721896936-chat-5.js
Normal file
16
packages/backend/migration/1742721896936-chat-5.js
Normal file
@@ -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"`);
|
||||
}
|
||||
}
|
@@ -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<Packed<'ChatMessageLite'>> {
|
||||
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<Packed<'ChatMessageLite'>> {
|
||||
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<MiChatMessage[]> {
|
||||
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
|
||||
|
@@ -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)),
|
||||
]);
|
||||
|
@@ -37,4 +37,9 @@ export class MiChatRoomInvitation {
|
||||
})
|
||||
@JoinColumn()
|
||||
public room: MiChatRoom | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public ignored: boolean;
|
||||
}
|
||||
|
@@ -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';
|
||||
|
@@ -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<typeof meta, typeof paramDef> { // 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,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -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,7 +103,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
if (ps.toUserId != null) {
|
||||
// Myself
|
||||
if (ps.toUserId === me.id) {
|
||||
throw new ApiError(meta.errors.recipientIsYourself);
|
||||
@@ -129,24 +117,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
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);
|
||||
}
|
||||
}*/
|
||||
});
|
||||
}
|
||||
}
|
@@ -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<typeof meta, typeof paramDef> { // 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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -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<typeof meta, typeof paramDef> { // 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,
|
||||
})));
|
||||
}*/
|
||||
});
|
||||
}
|
||||
}
|
@@ -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<typeof meta, typeof paramDef> { // 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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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);
|
||||
});
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // 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);
|
||||
});
|
||||
}
|
||||
}
|
204
packages/frontend/src/pages/chat/home.history.vue
Normal file
204
packages/frontend/src/pages/chat/home.history.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<MkButton primary gradate :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
|
||||
|
||||
<div v-if="history.length > 0" class="_gaps_s">
|
||||
<MkA
|
||||
v-for="item in history"
|
||||
:key="item.id"
|
||||
:class="[$style.message, { [$style.isMe]: item.isMe, [$style.isRead]: item.message.isRead }]"
|
||||
class="_panel"
|
||||
:to="item.message.toRoomId ? `/chat/room/${item.message.toRoomId}` : `/chat/user/${item.other!.id}`"
|
||||
>
|
||||
<MkAvatar v-if="item.other" :class="$style.messageAvatar" :user="item.other" indicator :preview="false"/>
|
||||
<div :class="$style.messageBody">
|
||||
<header v-if="item.message.toRoom" :class="$style.messageHeader">
|
||||
<span :class="$style.messageHeaderName">{{ item.message.toRoom.name }}</span>
|
||||
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||
</header>
|
||||
<header v-else :class="$style.messageHeader">
|
||||
<MkUserName :class="$style.messageHeaderName" :user="item.other!"/>
|
||||
<MkAcct :class="$style.messageHeaderUsername" :user="item.other!"/>
|
||||
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||
</header>
|
||||
<div :class="$style.messageBodyText"><span v-if="item.isMe" :class="$style.youSaid">{{ i18n.ts.you }}:</span>{{ item.message.text }}</div>
|
||||
</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<div v-if="!fetching && history.length == 0" class="_fullinfo">
|
||||
<div>{{ i18n.ts._chat.noHistory }}</div>
|
||||
</div>
|
||||
<MkLoading v-if="fetching"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import * as os from '@/os.js';
|
||||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const fetching = ref(true);
|
||||
const history = ref<{
|
||||
id: string;
|
||||
message: Misskey.entities.ChatMessage;
|
||||
other: Misskey.entities.ChatMessage['fromUser'] | Misskey.entities.ChatMessage['toUser'] | null;
|
||||
isMe: boolean;
|
||||
}[]>([]);
|
||||
|
||||
function start(ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts._chat.individualChat,
|
||||
caption: i18n.ts._chat.individualChat_description,
|
||||
icon: 'ti ti-user',
|
||||
action: () => { startUser(); },
|
||||
}, { type: 'divider' }, {
|
||||
type: 'parent',
|
||||
text: i18n.ts._chat.roomChat,
|
||||
caption: i18n.ts._chat.roomChat_description,
|
||||
icon: 'ti ti-users-group',
|
||||
children: [{
|
||||
text: i18n.ts._chat.createRoom,
|
||||
icon: 'ti ti-plus',
|
||||
action: () => { createRoom(); },
|
||||
}],
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function startUser() {
|
||||
os.selectUser().then(user => {
|
||||
router.push(`/chat/user/${user.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
async function createRoom() {
|
||||
const { canceled, result } = await os.inputText({
|
||||
title: i18n.ts.name,
|
||||
minLength: 1,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const room = await misskeyApi('chat/rooms/create', {
|
||||
name: result,
|
||||
});
|
||||
|
||||
router.push(`/chat/room/${room.id}`);
|
||||
}
|
||||
|
||||
async function fetchHistory() {
|
||||
fetching.value = true;
|
||||
|
||||
const [userMessages, roomMessages] = await Promise.all([
|
||||
misskeyApi('chat/history', { room: false }),
|
||||
misskeyApi('chat/history', { room: true }),
|
||||
]);
|
||||
|
||||
history.value = [...userMessages, ...roomMessages]
|
||||
.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
.map(m => ({
|
||||
id: m.id,
|
||||
message: m,
|
||||
other: m.room == null ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
|
||||
isMe: m.fromUserId === $i.id,
|
||||
}));
|
||||
|
||||
fetching.value = false;
|
||||
|
||||
updateCurrentAccountPartial({ hasUnreadChatMessages: false });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchHistory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.start {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.message {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 16px 24px;
|
||||
|
||||
&.isRead,
|
||||
&.isMe {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:not(.isMe):not(.isRead) {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
background-color: var(--MI_THEME-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageAvatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 16px 0 0;
|
||||
}
|
||||
|
||||
.messageBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.messageHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.messageHeaderName {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.messageHeaderUsername {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.messageHeaderTime {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.messageBodyText {
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.youSaid {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
96
packages/frontend/src/pages/chat/home.invitations.vue
Normal file
96
packages/frontend/src/pages/chat/home.invitations.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<div v-if="invitations.length > 0" class="_gaps_s">
|
||||
<MkFolder v-for="invitation in invitations" :key="invitation.id" :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-users-group"></i></template>
|
||||
<template #label>{{ invitation.room.name }}</template>
|
||||
<template #suffix><MkTime :time="invitation.createdAt"/></template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton primary @click="join(invitation)"><i class="ti ti-plus"></i> {{ i18n.ts._chat.join }}</MkButton>
|
||||
<MkButton danger @click="ignore(invitation)"><i class="ti ti-x"></i> {{ i18n.ts._chat.ignore }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :class="$style.invitationBody">
|
||||
<MkAvatar :user="invitation.room.owner" :class="$style.invitationBodyAvatar" link/>
|
||||
<div>
|
||||
<MkUserName :user="invitation.room.owner"/>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
<div v-if="!fetching && invitations.length == 0" class="_fullinfo">
|
||||
<div>{{ i18n.ts._chat.noInvitations }}</div>
|
||||
</div>
|
||||
<MkLoading v-if="fetching"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const fetching = ref(true);
|
||||
const invitations = ref<Misskey.entities.ChatRoomInvitation[]>([]);
|
||||
|
||||
async function fetchInvitations() {
|
||||
fetching.value = true;
|
||||
|
||||
const res = await misskeyApi('chat/rooms/invitations/inbox', {
|
||||
});
|
||||
|
||||
invitations.value = res;
|
||||
|
||||
fetching.value = false;
|
||||
}
|
||||
|
||||
async function join(invitation: Misskey.entities.ChatRoomInvitation) {
|
||||
await misskeyApi('chat/rooms/join', {
|
||||
roomId: invitation.room.id,
|
||||
});
|
||||
|
||||
router.push(`/chat/room/${invitation.room.id}`);
|
||||
}
|
||||
|
||||
async function ignore(invitation: Misskey.entities.ChatRoomInvitation) {
|
||||
await misskeyApi('chat/rooms/invitations/ignore', {
|
||||
roomId: invitation.room.id,
|
||||
});
|
||||
|
||||
invitations.value = invitations.value.filter(i => i.id !== invitation.id);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchInvitations();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.invitationBody {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invitationBodyAvatar {
|
||||
margin-right: 12px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
57
packages/frontend/src/pages/chat/home.ownedRooms.vue
Normal file
57
packages/frontend/src/pages/chat/home.ownedRooms.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<div v-if="rooms.length > 0" class="_gaps_s">
|
||||
<MkA v-for="room in rooms" :key="room.id" :to="`/chat/room/${room.id}`" class="_panel" :class="$style.room">
|
||||
<div>{{ room.name }}</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<div v-if="!fetching && rooms.length == 0" class="_fullinfo">
|
||||
<div>{{ i18n.ts._chat.noRooms }}</div>
|
||||
</div>
|
||||
<MkLoading v-if="fetching"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const fetching = ref(true);
|
||||
const rooms = ref<Misskey.entities.ChatRoom[]>([]);
|
||||
|
||||
async function fetchRooms() {
|
||||
fetching.value = true;
|
||||
|
||||
const res = await misskeyApi('chat/rooms/owned', {
|
||||
});
|
||||
|
||||
rooms.value = res;
|
||||
|
||||
fetching.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRooms();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.room {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
@@ -4,139 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<MkSpacer :contentMax="700">
|
||||
<div class="_gaps">
|
||||
<MkButton primary gradate :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
|
||||
|
||||
<div v-if="history.length > 0" :class="$style.history">
|
||||
<MkA
|
||||
v-for="item in history"
|
||||
:key="item.id"
|
||||
:class="[$style.message, { [$style.isMe]: item.isMe, [$style.isRead]: item.message.isRead }]"
|
||||
class="_panel"
|
||||
:to="item.message.toRoomId ? `/chat/room/${item.message.toRoomId}` : `/chat/user/${item.other!.id}`"
|
||||
>
|
||||
<MkAvatar v-if="item.other" :class="$style.messageAvatar" :user="item.other" indicator :preview="false"/>
|
||||
<div :class="$style.messageBody">
|
||||
<header v-if="item.message.room" :class="$style.messageHeader">
|
||||
<span :class="$style.messageHeaderName">{{ item.message.room.name }}</span>
|
||||
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||
</header>
|
||||
<header v-else :class="$style.messageHeader">
|
||||
<MkUserName :class="$style.messageHeaderName" :user="item.other!"/>
|
||||
<MkAcct :class="$style.messageHeaderUsername" :user="item.other!"/>
|
||||
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||
</header>
|
||||
<div :class="$style.messageBodyText"><span v-if="item.isMe" :class="$style.iSaid">{{ i18n.ts.you }}:</span>{{ item.message.text }}</div>
|
||||
</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<div v-if="!fetching && history.length == 0" class="_fullinfo">
|
||||
<div>{{ i18n.ts.noHistory }}</div>
|
||||
</div>
|
||||
<MkLoading v-if="fetching"/>
|
||||
</div>
|
||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||
<XHistory v-if="tab === 'home'"/>
|
||||
<XInvitations v-else-if="tab === 'invitations'"/>
|
||||
<XOwnedRooms v-else-if="tab === 'ownedRooms'"/>
|
||||
</MkHorizontalSwipe>
|
||||
</MkSpacer>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import XHistory from './home.history.vue';
|
||||
import XInvitations from './home.invitations.vue';
|
||||
import XOwnedRooms from './home.ownedRooms.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import * as os from '@/os.js';
|
||||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const fetching = ref(true);
|
||||
const history = ref<{
|
||||
id: string;
|
||||
message: Misskey.entities.ChatMessage;
|
||||
other: Misskey.entities.ChatMessage['fromUser'] | Misskey.entities.ChatMessage['toUser'] | null;
|
||||
isMe: boolean;
|
||||
}[]>([]);
|
||||
|
||||
function start(ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts._chat.individualChat,
|
||||
caption: i18n.ts._chat.individualChat_description,
|
||||
icon: 'ti ti-user',
|
||||
action: () => { startUser(); },
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts._chat.roomChat,
|
||||
caption: i18n.ts._chat.roomChat_description,
|
||||
icon: 'ti ti-users',
|
||||
action: () => { startRoom(); },
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function startUser() {
|
||||
os.selectUser().then(user => {
|
||||
router.push(`/chat/user/${user.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
async function startRoom() {
|
||||
/*
|
||||
const rooms1 = await os.api('users/rooms/owned');
|
||||
const rooms2 = await os.api('users/rooms/joined');
|
||||
if (rooms1.length === 0 && rooms2.length === 0) {
|
||||
os.alert({
|
||||
type: 'warning',
|
||||
title: i18n.ts.youHaveNoGroups,
|
||||
text: i18n.ts.joinOrCreateGroup,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { canceled, result: room } = await os.select({
|
||||
title: i18n.ts.room,
|
||||
items: rooms1.concat(rooms2).map(room => ({
|
||||
value: room, text: room.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
router.push(`/chat/room/${room.id}`);
|
||||
*/
|
||||
}
|
||||
|
||||
async function fetchHistory() {
|
||||
fetching.value = true;
|
||||
|
||||
const [userMessages, roomMessages] = await Promise.all([
|
||||
misskeyApi('chat/history', { room: false }),
|
||||
misskeyApi('chat/history', { room: true }),
|
||||
]);
|
||||
|
||||
history.value = [...userMessages, ...roomMessages]
|
||||
.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
.map(m => ({
|
||||
id: m.id,
|
||||
message: m,
|
||||
other: m.room == null ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
|
||||
isMe: m.fromUserId === $i.id,
|
||||
}));
|
||||
|
||||
fetching.value = false;
|
||||
|
||||
updateCurrentAccountPartial({ hasUnreadChatMessages: false });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchHistory();
|
||||
});
|
||||
const tab = ref('home');
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'home',
|
||||
title: i18n.ts._chat.history,
|
||||
icon: 'ti ti-home',
|
||||
}, {
|
||||
key: 'invitations',
|
||||
title: i18n.ts._chat.invitations,
|
||||
icon: 'ti ti-ticket',
|
||||
}, {
|
||||
key: 'joiningRooms',
|
||||
title: i18n.ts._chat.joiningRooms,
|
||||
icon: 'ti ti-users-group',
|
||||
}, {
|
||||
key: 'ownedRooms',
|
||||
title: i18n.ts._chat.yourRooms,
|
||||
icon: 'ti ti-settings',
|
||||
}]);
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.chat,
|
||||
@@ -145,78 +53,4 @@ definePage(() => ({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.start {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.message {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 16px 24px;
|
||||
|
||||
&.isRead,
|
||||
&.isMe {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:not(.isMe):not(.isRead) {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
background-color: var(--MI_THEME-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageAvatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 16px 0 0;
|
||||
}
|
||||
|
||||
.messageBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.messageHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.messageHeaderName {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.messageHeaderUsername {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.messageHeaderTime {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.messageBodyText {
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.iSaid {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
@@ -168,9 +168,10 @@ 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,
|
||||
|
||||
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 => {
|
||||
@@ -180,6 +181,19 @@ function send() {
|
||||
}).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() {
|
||||
|
@@ -4,20 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader reversed>
|
||||
<PageWithHeader reversed :actions="headerActions">
|
||||
<MkSpacer :contentMax="700">
|
||||
<div v-if="initializing">
|
||||
<MkLoading/>
|
||||
</div>
|
||||
<div v-else-if="messages.length === 0">
|
||||
<div class="_gaps" style="text-align: center;">
|
||||
<div>{{ i18n.ts.noMessagesYet }}</div>
|
||||
<div>{{ i18n.ts._chat.noMessagesYet }}</div>
|
||||
<template v-if="user">
|
||||
<div v-if="user.chatScope === 'followers'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowers }}</div>
|
||||
<div v-else-if="user.chatScope === 'following'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowing }}</div>
|
||||
<div v-else-if="user.chatScope === 'mutual'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromMutualFollowing }}</div>
|
||||
<div v-else>{{ i18n.ts._chat.thisUserNotAllowedChatAnyone }}</div>
|
||||
</template>
|
||||
<template v-else-if="room">
|
||||
<div>{{ i18n.ts._chat.inviteUserToChat }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="_gaps">
|
||||
@@ -33,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:moveClass="prefer.s.animation ? $style.transition_x_move : ''"
|
||||
tag="div" class="_gaps"
|
||||
>
|
||||
<XMessage v-for="message in messages.toReversed()" :key="message.id" :message="message" :user="message.fromUserId === $i.id ? $i : user" :isRoom="room != null"/>
|
||||
<XMessage v-for="message in messages.toReversed()" :key="message.id" :message="message" :user="room != null ? message.fromUser : (message.fromUserId === $i.id ? $i : user)" :isRoom="room != null"/>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
@@ -61,6 +64,7 @@ import * as Misskey from 'misskey-js';
|
||||
import { isTailVisible } from '@@/js/scroll.js';
|
||||
import XMessage from './room.message.vue';
|
||||
import XForm from './room.form.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import * as sound from '@/utility/sound.js';
|
||||
@@ -84,7 +88,7 @@ const messages = ref<Misskey.entities.ChatMessage[]>([]);
|
||||
const canFetchMore = ref(false);
|
||||
const user = ref<Misskey.entities.UserDetailed | null>(null);
|
||||
const room = ref<Misskey.entities.ChatRoom | null>(null);
|
||||
const connection = ref<Misskey.ChannelConnection<Misskey.Channels['chat']> | null>(null);
|
||||
const connection = ref<Misskey.ChannelConnection<Misskey.Channels['chatUser'] | Misskey.Channels['chatRoom']> | null>(null);
|
||||
const showIndicator = ref(false);
|
||||
|
||||
async function initialize() {
|
||||
@@ -95,7 +99,7 @@ async function initialize() {
|
||||
if (props.userId) {
|
||||
const [u, m] = await Promise.all([
|
||||
misskeyApi('users/show', { userId: props.userId }),
|
||||
misskeyApi('chat/messages/timeline', { userId: props.userId, limit: LIMIT }),
|
||||
misskeyApi('chat/messages/user-timeline', { userId: props.userId, limit: LIMIT }),
|
||||
]);
|
||||
|
||||
user.value = u;
|
||||
@@ -113,7 +117,7 @@ async function initialize() {
|
||||
} else {
|
||||
const [r, m] = await Promise.all([
|
||||
misskeyApi('chat/rooms/show', { roomId: props.roomId }),
|
||||
misskeyApi('chat/messages/timeline', { roomId: props.roomId, limit: LIMIT }),
|
||||
misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }),
|
||||
]);
|
||||
|
||||
room.value = r;
|
||||
@@ -124,7 +128,7 @@ async function initialize() {
|
||||
}
|
||||
|
||||
connection.value = useStream().useChannel('chatRoom', {
|
||||
otherId: user.value.id,
|
||||
roomId: room.value.id,
|
||||
});
|
||||
connection.value.on('message', onMessage);
|
||||
connection.value.on('deleted', onDeleted);
|
||||
@@ -145,21 +149,25 @@ onDeactivated(() => {
|
||||
isActivated = false;
|
||||
});
|
||||
|
||||
function fetchMore() {
|
||||
async function fetchMore() {
|
||||
const LIMIT = 30;
|
||||
|
||||
moreFetching.value = true;
|
||||
|
||||
misskeyApi('chat/messages/timeline', {
|
||||
const newMessages = props.userId ? await misskeyApi('chat/messages/user-timeline', {
|
||||
userId: user.value.id,
|
||||
limit: LIMIT,
|
||||
untilId: messages.value[messages.value.length - 1].id,
|
||||
}).then(newMessages => {
|
||||
}) : await misskeyApi('chat/messages/room-timeline', {
|
||||
roomId: room.value.id,
|
||||
limit: LIMIT,
|
||||
untilId: messages.value[messages.value.length - 1].id,
|
||||
});
|
||||
|
||||
messages.value.push(...newMessages);
|
||||
|
||||
canFetchMore.value = newMessages.length === LIMIT;
|
||||
moreFetching.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(message: Misskey.entities.ChatMessage) {
|
||||
@@ -208,6 +216,37 @@ onBeforeUnmount(() => {
|
||||
window.document.removeEventListener('visibilitychange', onVisibilitychange);
|
||||
});
|
||||
|
||||
async function inviteUser() {
|
||||
const invitee = await os.selectUser({ includeSelf: false, localOnly: true });
|
||||
os.apiWithDialog('chat/rooms/invitations/create', {
|
||||
roomId: room.value?.id,
|
||||
userId: invitee.id,
|
||||
});
|
||||
}
|
||||
|
||||
function showMenu(ev: MouseEvent) {
|
||||
const menuItems: MenuItem[] = [];
|
||||
|
||||
if (room.value) {
|
||||
if (room.value.ownerId === $i.id) {
|
||||
menuItems.push({
|
||||
text: i18n.ts._chat.inviteUser,
|
||||
icon: 'ti ti-user-plus',
|
||||
action: () => {
|
||||
inviteUser();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
icon: 'ti ti-dots',
|
||||
handler: showMenu,
|
||||
}]);
|
||||
|
||||
definePage(computed(() => !initializing.value ? user.value ? {
|
||||
userName: user,
|
||||
avatar: user,
|
||||
|
@@ -960,10 +960,16 @@ type ChatMessage = components['schemas']['ChatMessage'];
|
||||
type ChatMessageLite = components['schemas']['ChatMessageLite'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesCreateRequest = operations['chat___messages___create']['requestBody']['content']['application/json'];
|
||||
type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesCreateResponse = operations['chat___messages___create']['responses']['200']['content']['application/json'];
|
||||
type ChatMessagesCreateToRoomResponse = operations['chat___messages___create-to-room']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesCreateToUserRequest = operations['chat___messages___create-to-user']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-user']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
|
||||
@@ -971,6 +977,12 @@ type ChatMessagesDeleteRequest = operations['chat___messages___delete']['request
|
||||
// @public (undocumented)
|
||||
type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesRoomTimelineResponse = operations['chat___messages___room-timeline']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
|
||||
|
||||
@@ -978,10 +990,10 @@ type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody
|
||||
type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesTimelineRequest = operations['chat___messages___timeline']['requestBody']['content']['application/json'];
|
||||
type ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatMessagesTimelineResponse = operations['chat___messages___timeline']['responses']['200']['content']['application/json'];
|
||||
type ChatMessagesUserTimelineResponse = operations['chat___messages___user-timeline']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoom = components['schemas']['ChatRoom'];
|
||||
@@ -1010,18 +1022,18 @@ type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations_
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsRejectRequest = operations['chat___rooms___invitations___reject']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsInvitationsRejectResponse = operations['chat___rooms___invitations___reject']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
|
||||
|
||||
@@ -1557,24 +1569,28 @@ declare namespace entities {
|
||||
ChartsUsersResponse,
|
||||
ChatHistoryRequest,
|
||||
ChatHistoryResponse,
|
||||
ChatMessagesCreateRequest,
|
||||
ChatMessagesCreateResponse,
|
||||
ChatMessagesCreateToRoomRequest,
|
||||
ChatMessagesCreateToRoomResponse,
|
||||
ChatMessagesCreateToUserRequest,
|
||||
ChatMessagesCreateToUserResponse,
|
||||
ChatMessagesDeleteRequest,
|
||||
ChatMessagesDeleteResponse,
|
||||
ChatMessagesRoomTimelineRequest,
|
||||
ChatMessagesRoomTimelineResponse,
|
||||
ChatMessagesShowRequest,
|
||||
ChatMessagesShowResponse,
|
||||
ChatMessagesTimelineRequest,
|
||||
ChatMessagesTimelineResponse,
|
||||
ChatMessagesUserTimelineRequest,
|
||||
ChatMessagesUserTimelineResponse,
|
||||
ChatRoomsCreateRequest,
|
||||
ChatRoomsCreateResponse,
|
||||
ChatRoomsDeleteRequest,
|
||||
ChatRoomsDeleteResponse,
|
||||
ChatRoomsInvitationsCreateRequest,
|
||||
ChatRoomsInvitationsCreateResponse,
|
||||
ChatRoomsInvitationsIgnoreRequest,
|
||||
ChatRoomsInvitationsIgnoreResponse,
|
||||
ChatRoomsInvitationsInboxRequest,
|
||||
ChatRoomsInvitationsInboxResponse,
|
||||
ChatRoomsInvitationsRejectRequest,
|
||||
ChatRoomsInvitationsRejectResponse,
|
||||
ChatRoomsJoinRequest,
|
||||
ChatRoomsJoinResponse,
|
||||
ChatRoomsLeaveRequest,
|
||||
|
@@ -1550,7 +1550,18 @@ declare module '../api.js' {
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
request<E extends 'chat/messages/create', P extends Endpoints[E]['req']>(
|
||||
request<E extends 'chat/messages/create-to-room', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
request<E extends 'chat/messages/create-to-user', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
@@ -1567,6 +1578,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/room-timeline', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
@@ -1583,7 +1605,7 @@ declare module '../api.js' {
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
request<E extends 'chat/messages/timeline', P extends Endpoints[E]['req']>(
|
||||
request<E extends 'chat/messages/user-timeline', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
@@ -1625,9 +1647,9 @@ declare module '../api.js' {
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
request<E extends 'chat/rooms/invitations/inbox', P extends Endpoints[E]['req']>(
|
||||
request<E extends 'chat/rooms/invitations/ignore', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
@@ -1636,9 +1658,9 @@ declare module '../api.js' {
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
request<E extends 'chat/rooms/invitations/reject', P extends Endpoints[E]['req']>(
|
||||
request<E extends 'chat/rooms/invitations/inbox', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
|
@@ -209,24 +209,28 @@ import type {
|
||||
ChartsUsersResponse,
|
||||
ChatHistoryRequest,
|
||||
ChatHistoryResponse,
|
||||
ChatMessagesCreateRequest,
|
||||
ChatMessagesCreateResponse,
|
||||
ChatMessagesCreateToRoomRequest,
|
||||
ChatMessagesCreateToRoomResponse,
|
||||
ChatMessagesCreateToUserRequest,
|
||||
ChatMessagesCreateToUserResponse,
|
||||
ChatMessagesDeleteRequest,
|
||||
ChatMessagesDeleteResponse,
|
||||
ChatMessagesRoomTimelineRequest,
|
||||
ChatMessagesRoomTimelineResponse,
|
||||
ChatMessagesShowRequest,
|
||||
ChatMessagesShowResponse,
|
||||
ChatMessagesTimelineRequest,
|
||||
ChatMessagesTimelineResponse,
|
||||
ChatMessagesUserTimelineRequest,
|
||||
ChatMessagesUserTimelineResponse,
|
||||
ChatRoomsCreateRequest,
|
||||
ChatRoomsCreateResponse,
|
||||
ChatRoomsDeleteRequest,
|
||||
ChatRoomsDeleteResponse,
|
||||
ChatRoomsInvitationsCreateRequest,
|
||||
ChatRoomsInvitationsCreateResponse,
|
||||
ChatRoomsInvitationsIgnoreRequest,
|
||||
ChatRoomsInvitationsIgnoreResponse,
|
||||
ChatRoomsInvitationsInboxRequest,
|
||||
ChatRoomsInvitationsInboxResponse,
|
||||
ChatRoomsInvitationsRejectRequest,
|
||||
ChatRoomsInvitationsRejectResponse,
|
||||
ChatRoomsJoinRequest,
|
||||
ChatRoomsJoinResponse,
|
||||
ChatRoomsLeaveRequest,
|
||||
@@ -759,15 +763,17 @@ export type Endpoints = {
|
||||
'charts/user/reactions': { req: ChartsUserReactionsRequest; res: ChartsUserReactionsResponse };
|
||||
'charts/users': { req: ChartsUsersRequest; res: ChartsUsersResponse };
|
||||
'chat/history': { req: ChatHistoryRequest; res: ChatHistoryResponse };
|
||||
'chat/messages/create': { req: ChatMessagesCreateRequest; res: ChatMessagesCreateResponse };
|
||||
'chat/messages/create-to-room': { req: ChatMessagesCreateToRoomRequest; res: ChatMessagesCreateToRoomResponse };
|
||||
'chat/messages/create-to-user': { req: ChatMessagesCreateToUserRequest; res: ChatMessagesCreateToUserResponse };
|
||||
'chat/messages/delete': { req: ChatMessagesDeleteRequest; res: ChatMessagesDeleteResponse };
|
||||
'chat/messages/room-timeline': { req: ChatMessagesRoomTimelineRequest; res: ChatMessagesRoomTimelineResponse };
|
||||
'chat/messages/show': { req: ChatMessagesShowRequest; res: ChatMessagesShowResponse };
|
||||
'chat/messages/timeline': { req: ChatMessagesTimelineRequest; res: ChatMessagesTimelineResponse };
|
||||
'chat/messages/user-timeline': { req: ChatMessagesUserTimelineRequest; res: ChatMessagesUserTimelineResponse };
|
||||
'chat/rooms/create': { req: ChatRoomsCreateRequest; res: ChatRoomsCreateResponse };
|
||||
'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: ChatRoomsDeleteResponse };
|
||||
'chat/rooms/invitations/create': { req: ChatRoomsInvitationsCreateRequest; res: ChatRoomsInvitationsCreateResponse };
|
||||
'chat/rooms/invitations/ignore': { req: ChatRoomsInvitationsIgnoreRequest; res: ChatRoomsInvitationsIgnoreResponse };
|
||||
'chat/rooms/invitations/inbox': { req: ChatRoomsInvitationsInboxRequest; res: ChatRoomsInvitationsInboxResponse };
|
||||
'chat/rooms/invitations/reject': { req: ChatRoomsInvitationsRejectRequest; res: ChatRoomsInvitationsRejectResponse };
|
||||
'chat/rooms/join': { req: ChatRoomsJoinRequest; res: ChatRoomsJoinResponse };
|
||||
'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse };
|
||||
'chat/rooms/members': { req: ChatRoomsMembersRequest; res: ChatRoomsMembersResponse };
|
||||
|
@@ -212,24 +212,28 @@ export type ChartsUsersRequest = operations['charts___users']['requestBody']['co
|
||||
export type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json'];
|
||||
export type ChatHistoryRequest = operations['chat___history']['requestBody']['content']['application/json'];
|
||||
export type ChatHistoryResponse = operations['chat___history']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesCreateRequest = operations['chat___messages___create']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesCreateResponse = operations['chat___messages___create']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesCreateToRoomResponse = operations['chat___messages___create-to-room']['responses']['200']['content']['application/json'];
|
||||
export type ChatMessagesCreateToUserRequest = operations['chat___messages___create-to-user']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-user']['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 ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesRoomTimelineResponse = operations['chat___messages___room-timeline']['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 ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
|
||||
export type ChatMessagesUserTimelineResponse = operations['chat___messages___user-timeline']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsCreateRequest = operations['chat___rooms___create']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations___create']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsRejectRequest = operations['chat___rooms___invitations___reject']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsInvitationsRejectResponse = operations['chat___rooms___invitations___reject']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
|
||||
export type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['content']['application/json'];
|
||||
export type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];
|
||||
|
@@ -1367,14 +1367,23 @@ export type paths = {
|
||||
*/
|
||||
post: operations['chat___history'];
|
||||
};
|
||||
'/chat/messages/create': {
|
||||
'/chat/messages/create-to-room': {
|
||||
/**
|
||||
* chat/messages/create
|
||||
* chat/messages/create-to-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
post: operations['chat___messages___create'];
|
||||
post: operations['chat___messages___create-to-room'];
|
||||
};
|
||||
'/chat/messages/create-to-user': {
|
||||
/**
|
||||
* chat/messages/create-to-user
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
post: operations['chat___messages___create-to-user'];
|
||||
};
|
||||
'/chat/messages/delete': {
|
||||
/**
|
||||
@@ -1385,6 +1394,15 @@ export type paths = {
|
||||
*/
|
||||
post: operations['chat___messages___delete'];
|
||||
};
|
||||
'/chat/messages/room-timeline': {
|
||||
/**
|
||||
* chat/messages/room-timeline
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
post: operations['chat___messages___room-timeline'];
|
||||
};
|
||||
'/chat/messages/show': {
|
||||
/**
|
||||
* chat/messages/show
|
||||
@@ -1394,14 +1412,14 @@ export type paths = {
|
||||
*/
|
||||
post: operations['chat___messages___show'];
|
||||
};
|
||||
'/chat/messages/timeline': {
|
||||
'/chat/messages/user-timeline': {
|
||||
/**
|
||||
* chat/messages/timeline
|
||||
* chat/messages/user-timeline
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
post: operations['chat___messages___timeline'];
|
||||
post: operations['chat___messages___user-timeline'];
|
||||
};
|
||||
'/chat/rooms/create': {
|
||||
/**
|
||||
@@ -1430,6 +1448,15 @@ export type paths = {
|
||||
*/
|
||||
post: operations['chat___rooms___invitations___create'];
|
||||
};
|
||||
'/chat/rooms/invitations/ignore': {
|
||||
/**
|
||||
* chat/rooms/invitations/ignore
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
post: operations['chat___rooms___invitations___ignore'];
|
||||
};
|
||||
'/chat/rooms/invitations/inbox': {
|
||||
/**
|
||||
* chat/rooms/invitations/inbox
|
||||
@@ -1439,15 +1466,6 @@ export type paths = {
|
||||
*/
|
||||
post: operations['chat___rooms___invitations___inbox'];
|
||||
};
|
||||
'/chat/rooms/invitations/reject': {
|
||||
/**
|
||||
* chat/rooms/invitations/reject
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
post: operations['chat___rooms___invitations___reject'];
|
||||
};
|
||||
'/chat/rooms/join': {
|
||||
/**
|
||||
* chat/rooms/join
|
||||
@@ -13915,12 +13933,12 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/create
|
||||
* chat/messages/create-to-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
chat___messages___create: {
|
||||
'chat___messages___create-to-room': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
@@ -13928,7 +13946,70 @@ export type operations = {
|
||||
/** Format: misskey:id */
|
||||
fileId?: string;
|
||||
/** Format: misskey:id */
|
||||
toUserId?: string | null;
|
||||
toRoomId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['ChatMessageLite'];
|
||||
};
|
||||
};
|
||||
/** @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 Too many requests */
|
||||
429: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/create-to-user
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
'chat___messages___create-to-user': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
text?: string | null;
|
||||
/** Format: misskey:id */
|
||||
fileId?: string;
|
||||
/** Format: misskey:id */
|
||||
toUserId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -14031,6 +14112,66 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/room-timeline
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
'chat___messages___room-timeline': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
/** Format: misskey:id */
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
/** Format: misskey:id */
|
||||
roomId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['ChatMessageLite'][];
|
||||
};
|
||||
};
|
||||
/** @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/show
|
||||
* @description No description provided.
|
||||
@@ -14086,12 +14227,12 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/messages/timeline
|
||||
* chat/messages/user-timeline
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
chat___messages___timeline: {
|
||||
'chat___messages___user-timeline': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
@@ -14102,7 +14243,7 @@ export type operations = {
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
/** Format: misskey:id */
|
||||
userId?: string | null;
|
||||
userId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -14321,21 +14462,17 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/rooms/invitations/inbox
|
||||
* chat/rooms/invitations/ignore
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
*/
|
||||
chat___rooms___invitations___inbox: {
|
||||
chat___rooms___invitations___ignore: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** @default 30 */
|
||||
limit?: number;
|
||||
/** Format: misskey:id */
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
roomId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -14343,7 +14480,7 @@ export type operations = {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['ChatRoomInvitation'][];
|
||||
'application/json': unknown;
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
@@ -14379,17 +14516,21 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chat/rooms/invitations/reject
|
||||
* chat/rooms/invitations/inbox
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:chat*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:chat*
|
||||
*/
|
||||
chat___rooms___invitations___reject: {
|
||||
chat___rooms___invitations___inbox: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** @default 30 */
|
||||
limit?: number;
|
||||
/** Format: misskey:id */
|
||||
roomId: string;
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -14397,7 +14538,7 @@ export type operations = {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': unknown;
|
||||
'application/json': components['schemas']['ChatRoomInvitation'][];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
|
Reference in New Issue
Block a user