This commit is contained in:
syuilo
2025-03-23 18:34:25 +09:00
parent b98d3cbc29
commit 9b9ff81201
25 changed files with 1096 additions and 465 deletions

48
locales/index.d.ts vendored
View File

@@ -5376,6 +5376,54 @@ export interface Locale extends ILocale {
* また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。 * また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。
*/ */
"roomChat_description": string; "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;
/** /**
* このユーザーとのチャットを開始できません * このユーザーとのチャットを開始できません
*/ */

View File

@@ -1341,6 +1341,18 @@ _chat:
individualChat_description: "特定ユーザーとの一対一のチャットができます。" individualChat_description: "特定ユーザーとの一対一のチャットができます。"
roomChat: "ルームチャット" roomChat: "ルームチャット"
roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。" roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。"
createRoom: "ルームを作成"
inviteUserToChat: "ユーザーを招待してチャットを始めましょう"
yourRooms: "作成したルーム"
joiningRooms: "参加中のルーム"
invitations: "招待"
noInvitations: "招待はありません"
history: "履歴"
noHistory: "履歴はありません"
noRooms: "ルームはありません"
inviteUser: "ユーザーを招待"
join: "参加"
ignore: "無視"
cannotChatWithTheUser: "このユーザーとのチャットを開始できません" cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
chatWithThisUser: "チャットする" chatWithThisUser: "チャットする"

View 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"`);
}
}

View File

@@ -22,6 +22,7 @@ import { QueryService } from '@/core/QueryService.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js';
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js'; import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
import { Packed } from '@/misc/json-schema.js';
const MAX_ROOM_MEMBERS = 30; const MAX_ROOM_MEMBERS = 30;
@@ -74,7 +75,7 @@ export class ChatService {
text?: string | null; text?: string | null;
file?: MiDriveFile | null; file?: MiDriveFile | null;
uri?: string | null; uri?: string | null;
}) { }): Promise<Packed<'ChatMessageLite'>> {
if (fromUser.id === toUser.id) { if (fromUser.id === toUser.id) {
throw new Error('yourself'); throw new Error('yourself');
} }
@@ -185,7 +186,7 @@ export class ChatService {
text?: string | null; text?: string | null;
file?: MiDriveFile | null; file?: MiDriveFile | null;
uri?: string | null; uri?: string | null;
}) { }): Promise<Packed<'ChatMessageLite'>> {
const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id }); const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id });
if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) { if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) {
@@ -294,7 +295,7 @@ export class ChatService {
} }
@bindThis @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) const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId)
.andWhere(new Brackets(qb => { .andWhere(new Brackets(qb => {
qb qb
@@ -317,6 +318,16 @@ export class ChatService {
return messages; 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 @bindThis
public async userHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> { public async userHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
const history: MiChatMessage[] = []; const history: MiChatMessage[] = [];
@@ -525,7 +536,8 @@ export class ChatService {
@bindThis @bindThis
public async getReceivedRoomInvitationsWithPagination(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) { 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) 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(); const invitations = await query.take(limit).getMany();
@@ -553,9 +565,9 @@ export class ChatService {
} }
@bindThis @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 }); const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
await this.chatRoomInvitationsRepository.delete(invitation.id); await this.chatRoomInvitationsRepository.update(invitation.id, { ignored: true });
} }
@bindThis @bindThis

View File

@@ -154,8 +154,7 @@ export class ChatEntityService {
createdAt: this.idService.parse(message.id).date.toISOString(), createdAt: this.idService.parse(message.id).date.toISOString(),
text: message.text, text: message.text,
fromUserId: message.fromUserId, fromUserId: message.fromUserId,
toUserId: message.toUserId, fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
toUser: packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId),
toRoomId: message.toRoomId, toRoomId: message.toRoomId,
fileId: message.fileId, fileId: message.fileId,
file: message.file ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file)) : null, 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 []; if (messages.length === 0) return [];
const [packedUsers, packedFiles] = await Promise.all([ 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]))), .then(users => new Map(users.map(u => [u.id, u]))),
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)), this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null)),
]); ]);

View File

@@ -37,4 +37,9 @@ export class MiChatRoomInvitation {
}) })
@JoinColumn() @JoinColumn()
public room: MiChatRoom | null; public room: MiChatRoom | null;
@Column('boolean', {
default: false,
})
public ignored: boolean;
} }

View File

@@ -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/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/show' from './endpoints/users/show.js';
export * as 'users/update-memo' from './endpoints/users/update-memo.js'; export * as 'users/update-memo' from './endpoints/users/update-memo.js';
export * as 'chat/messages/create' from './endpoints/chat/messages/create.js'; export * as 'chat/messages/create-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/delete' from './endpoints/chat/messages/delete.js';
export * as 'chat/messages/show' from './endpoints/chat/messages/show.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/create' from './endpoints/chat/rooms/create.js';
export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js'; export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js';
export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.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/update' from './endpoints/chat/rooms/update.js';
export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.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/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/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
export * as 'chat/history' from './endpoints/chat/history.js'; export * as 'chat/history' from './endpoints/chat/history.js';
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js'; export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';

View File

@@ -0,0 +1,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,
});
});
}
}

View File

@@ -46,18 +46,6 @@ export const meta = {
id: '11795c64-40ea-4198-b06e-3c873ed9039d', 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: { noSuchFile: {
message: 'No such file.', message: 'No such file.',
code: 'NO_SUCH_FILE', code: 'NO_SUCH_FILE',
@@ -83,8 +71,9 @@ export const paramDef = {
properties: { properties: {
text: { type: 'string', nullable: true, maxLength: 2000 }, text: { type: 'string', nullable: true, maxLength: 2000 },
fileId: { type: 'string', format: 'misskey:id' }, fileId: { type: 'string', format: 'misskey:id' },
toUserId: { type: 'string', format: 'misskey:id', nullable: true }, toUserId: { type: 'string', format: 'misskey:id' },
}, },
required: ['toUserId'],
} as const; } as const;
@Injectable() @Injectable()
@@ -114,7 +103,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.contentRequired); throw new ApiError(meta.errors.contentRequired);
} }
if (ps.toUserId != null) {
// Myself // Myself
if (ps.toUserId === me.id) { if (ps.toUserId === me.id) {
throw new ApiError(meta.errors.recipientIsYourself); throw new ApiError(meta.errors.recipientIsYourself);
@@ -129,24 +117,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
text: ps.text, text: ps.text,
file: file, 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);
}
}*/
}); });
} }
} }

View File

@@ -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);
});
}
}

View File

@@ -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,
})));
}*/
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (room == null) { if (room == null) {
throw new ApiError(meta.errors.noSuchRoom); 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); return await this.chatEntityService.packRoomInvitation(invitation, me);
}); });
} }

View File

@@ -42,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private chatService: ChatService, private chatService: ChatService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
await this.chatService.rejectRoomInvitation(me.id, ps.roomId); await this.chatService.ignoreRoomInvitation(me.id, ps.roomId);
}); });
} }
} }

View 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>

View 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>

View 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>

View File

@@ -4,139 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs"> <PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700"> <MkSpacer :contentMax="700">
<div class="_gaps"> <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<MkButton primary gradate :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton> <XHistory v-if="tab === 'home'"/>
<XInvitations v-else-if="tab === 'invitations'"/>
<div v-if="history.length > 0" :class="$style.history"> <XOwnedRooms v-else-if="tab === 'ownedRooms'"/>
<MkA </MkHorizontalSwipe>
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>
</MkSpacer> </MkSpacer>
</PageWithHeader> </PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js'; import XHistory from './home.history.vue';
import MkButton from '@/components/MkButton.vue'; import XInvitations from './home.invitations.vue';
import XOwnedRooms from './home.ownedRooms.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
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 tab = ref('home');
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 headerActions = computed(() => []); 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(() => ({ definePage(() => ({
title: i18n.ts.chat, title: i18n.ts.chat,
@@ -145,78 +53,4 @@ definePage(() => ({
</script> </script>
<style lang="scss" module> <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> </style>

View File

@@ -168,9 +168,10 @@ function send() {
if (!canSend.value) return; if (!canSend.value) return;
sending.value = true; sending.value = true;
misskeyApi('chat/messages/create', {
toUserId: props.user ? props.user.id : undefined, if (props.user) {
toRoomId: props.room ? props.room.id : undefined, misskeyApi('chat/messages/create-to-user', {
toUserId: props.user.id,
text: text.value ? text.value : undefined, text: text.value ? text.value : undefined,
fileId: file.value ? file.value.id : undefined, fileId: file.value ? file.value.id : undefined,
}).then(message => { }).then(message => {
@@ -180,6 +181,19 @@ function send() {
}).then(() => { }).then(() => {
sending.value = false; 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() { function clear() {

View File

@@ -4,20 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<PageWithHeader reversed> <PageWithHeader reversed :actions="headerActions">
<MkSpacer :contentMax="700"> <MkSpacer :contentMax="700">
<div v-if="initializing"> <div v-if="initializing">
<MkLoading/> <MkLoading/>
</div> </div>
<div v-else-if="messages.length === 0"> <div v-else-if="messages.length === 0">
<div class="_gaps" style="text-align: center;"> <div class="_gaps" style="text-align: center;">
<div>{{ i18n.ts.noMessagesYet }}</div> <div>{{ i18n.ts._chat.noMessagesYet }}</div>
<template v-if="user"> <template v-if="user">
<div v-if="user.chatScope === 'followers'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowers }}</div> <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 === 'following'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowing }}</div>
<div v-else-if="user.chatScope === 'mutual'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromMutualFollowing }}</div> <div v-else-if="user.chatScope === 'mutual'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromMutualFollowing }}</div>
<div v-else>{{ i18n.ts._chat.thisUserNotAllowedChatAnyone }}</div> <div v-else>{{ i18n.ts._chat.thisUserNotAllowedChatAnyone }}</div>
</template> </template>
<template v-else-if="room">
<div>{{ i18n.ts._chat.inviteUserToChat }}</div>
</template>
</div> </div>
</div> </div>
<div v-else class="_gaps"> <div v-else class="_gaps">
@@ -33,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:moveClass="prefer.s.animation ? $style.transition_x_move : ''" :moveClass="prefer.s.animation ? $style.transition_x_move : ''"
tag="div" class="_gaps" 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> </TransitionGroup>
</div> </div>
</MkSpacer> </MkSpacer>
@@ -61,6 +64,7 @@ import * as Misskey from 'misskey-js';
import { isTailVisible } from '@@/js/scroll.js'; import { isTailVisible } from '@@/js/scroll.js';
import XMessage from './room.message.vue'; import XMessage from './room.message.vue';
import XForm from './room.form.vue'; import XForm from './room.form.vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
import * as sound from '@/utility/sound.js'; import * as sound from '@/utility/sound.js';
@@ -84,7 +88,7 @@ const messages = ref<Misskey.entities.ChatMessage[]>([]);
const canFetchMore = ref(false); const canFetchMore = ref(false);
const user = ref<Misskey.entities.UserDetailed | null>(null); const user = ref<Misskey.entities.UserDetailed | null>(null);
const room = ref<Misskey.entities.ChatRoom | 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); const showIndicator = ref(false);
async function initialize() { async function initialize() {
@@ -95,7 +99,7 @@ async function initialize() {
if (props.userId) { if (props.userId) {
const [u, m] = await Promise.all([ const [u, m] = await Promise.all([
misskeyApi('users/show', { userId: props.userId }), 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; user.value = u;
@@ -113,7 +117,7 @@ async function initialize() {
} else { } else {
const [r, m] = await Promise.all([ const [r, m] = await Promise.all([
misskeyApi('chat/rooms/show', { roomId: props.roomId }), 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; room.value = r;
@@ -124,7 +128,7 @@ async function initialize() {
} }
connection.value = useStream().useChannel('chatRoom', { connection.value = useStream().useChannel('chatRoom', {
otherId: user.value.id, roomId: room.value.id,
}); });
connection.value.on('message', onMessage); connection.value.on('message', onMessage);
connection.value.on('deleted', onDeleted); connection.value.on('deleted', onDeleted);
@@ -145,21 +149,25 @@ onDeactivated(() => {
isActivated = false; isActivated = false;
}); });
function fetchMore() { async function fetchMore() {
const LIMIT = 30; const LIMIT = 30;
moreFetching.value = true; moreFetching.value = true;
misskeyApi('chat/messages/timeline', { const newMessages = props.userId ? await misskeyApi('chat/messages/user-timeline', {
userId: user.value.id, userId: user.value.id,
limit: LIMIT, limit: LIMIT,
untilId: messages.value[messages.value.length - 1].id, 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); messages.value.push(...newMessages);
canFetchMore.value = newMessages.length === LIMIT; canFetchMore.value = newMessages.length === LIMIT;
moreFetching.value = false; moreFetching.value = false;
});
} }
function onMessage(message: Misskey.entities.ChatMessage) { function onMessage(message: Misskey.entities.ChatMessage) {
@@ -208,6 +216,37 @@ onBeforeUnmount(() => {
window.document.removeEventListener('visibilitychange', onVisibilitychange); 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 ? { definePage(computed(() => !initializing.value ? user.value ? {
userName: user, userName: user,
avatar: user, avatar: user,

View File

@@ -960,10 +960,16 @@ type ChatMessage = components['schemas']['ChatMessage'];
type ChatMessageLite = components['schemas']['ChatMessageLite']; type ChatMessageLite = components['schemas']['ChatMessageLite'];
// @public (undocumented) // @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) // @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) // @public (undocumented)
type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json']; type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
@@ -971,6 +977,12 @@ type ChatMessagesDeleteRequest = operations['chat___messages___delete']['request
// @public (undocumented) // @public (undocumented)
type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json']; 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) // @public (undocumented)
type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json']; 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']; type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
// @public (undocumented) // @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) // @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) // @public (undocumented)
type ChatRoom = components['schemas']['ChatRoom']; type ChatRoom = components['schemas']['ChatRoom'];
@@ -1010,18 +1022,18 @@ type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations_
// @public (undocumented) // @public (undocumented)
type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json']; 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) // @public (undocumented)
type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json']; type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json']; 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) // @public (undocumented)
type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json']; type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
@@ -1557,24 +1569,28 @@ declare namespace entities {
ChartsUsersResponse, ChartsUsersResponse,
ChatHistoryRequest, ChatHistoryRequest,
ChatHistoryResponse, ChatHistoryResponse,
ChatMessagesCreateRequest, ChatMessagesCreateToRoomRequest,
ChatMessagesCreateResponse, ChatMessagesCreateToRoomResponse,
ChatMessagesCreateToUserRequest,
ChatMessagesCreateToUserResponse,
ChatMessagesDeleteRequest, ChatMessagesDeleteRequest,
ChatMessagesDeleteResponse, ChatMessagesDeleteResponse,
ChatMessagesRoomTimelineRequest,
ChatMessagesRoomTimelineResponse,
ChatMessagesShowRequest, ChatMessagesShowRequest,
ChatMessagesShowResponse, ChatMessagesShowResponse,
ChatMessagesTimelineRequest, ChatMessagesUserTimelineRequest,
ChatMessagesTimelineResponse, ChatMessagesUserTimelineResponse,
ChatRoomsCreateRequest, ChatRoomsCreateRequest,
ChatRoomsCreateResponse, ChatRoomsCreateResponse,
ChatRoomsDeleteRequest, ChatRoomsDeleteRequest,
ChatRoomsDeleteResponse, ChatRoomsDeleteResponse,
ChatRoomsInvitationsCreateRequest, ChatRoomsInvitationsCreateRequest,
ChatRoomsInvitationsCreateResponse, ChatRoomsInvitationsCreateResponse,
ChatRoomsInvitationsIgnoreRequest,
ChatRoomsInvitationsIgnoreResponse,
ChatRoomsInvitationsInboxRequest, ChatRoomsInvitationsInboxRequest,
ChatRoomsInvitationsInboxResponse, ChatRoomsInvitationsInboxResponse,
ChatRoomsInvitationsRejectRequest,
ChatRoomsInvitationsRejectResponse,
ChatRoomsJoinRequest, ChatRoomsJoinRequest,
ChatRoomsJoinResponse, ChatRoomsJoinResponse,
ChatRoomsLeaveRequest, ChatRoomsLeaveRequest,

View File

@@ -1550,7 +1550,18 @@ declare module '../api.js' {
* *
* **Credential required**: *Yes* / **Permission**: *write:chat* * **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, endpoint: E,
params: P, params: P,
credential?: string | null, credential?: string | null,
@@ -1567,6 +1578,17 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): 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. * No description provided.
* *
@@ -1583,7 +1605,7 @@ declare module '../api.js' {
* *
* **Credential required**: *Yes* / **Permission**: *read:chat* * **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, endpoint: E,
params: P, params: P,
credential?: string | null, credential?: string | null,
@@ -1625,9 +1647,9 @@ declare module '../api.js' {
/** /**
* No description provided. * 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, endpoint: E,
params: P, params: P,
credential?: string | null, credential?: string | null,
@@ -1636,9 +1658,9 @@ declare module '../api.js' {
/** /**
* No description provided. * 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, endpoint: E,
params: P, params: P,
credential?: string | null, credential?: string | null,

View File

@@ -209,24 +209,28 @@ import type {
ChartsUsersResponse, ChartsUsersResponse,
ChatHistoryRequest, ChatHistoryRequest,
ChatHistoryResponse, ChatHistoryResponse,
ChatMessagesCreateRequest, ChatMessagesCreateToRoomRequest,
ChatMessagesCreateResponse, ChatMessagesCreateToRoomResponse,
ChatMessagesCreateToUserRequest,
ChatMessagesCreateToUserResponse,
ChatMessagesDeleteRequest, ChatMessagesDeleteRequest,
ChatMessagesDeleteResponse, ChatMessagesDeleteResponse,
ChatMessagesRoomTimelineRequest,
ChatMessagesRoomTimelineResponse,
ChatMessagesShowRequest, ChatMessagesShowRequest,
ChatMessagesShowResponse, ChatMessagesShowResponse,
ChatMessagesTimelineRequest, ChatMessagesUserTimelineRequest,
ChatMessagesTimelineResponse, ChatMessagesUserTimelineResponse,
ChatRoomsCreateRequest, ChatRoomsCreateRequest,
ChatRoomsCreateResponse, ChatRoomsCreateResponse,
ChatRoomsDeleteRequest, ChatRoomsDeleteRequest,
ChatRoomsDeleteResponse, ChatRoomsDeleteResponse,
ChatRoomsInvitationsCreateRequest, ChatRoomsInvitationsCreateRequest,
ChatRoomsInvitationsCreateResponse, ChatRoomsInvitationsCreateResponse,
ChatRoomsInvitationsIgnoreRequest,
ChatRoomsInvitationsIgnoreResponse,
ChatRoomsInvitationsInboxRequest, ChatRoomsInvitationsInboxRequest,
ChatRoomsInvitationsInboxResponse, ChatRoomsInvitationsInboxResponse,
ChatRoomsInvitationsRejectRequest,
ChatRoomsInvitationsRejectResponse,
ChatRoomsJoinRequest, ChatRoomsJoinRequest,
ChatRoomsJoinResponse, ChatRoomsJoinResponse,
ChatRoomsLeaveRequest, ChatRoomsLeaveRequest,
@@ -759,15 +763,17 @@ export type Endpoints = {
'charts/user/reactions': { req: ChartsUserReactionsRequest; res: ChartsUserReactionsResponse }; 'charts/user/reactions': { req: ChartsUserReactionsRequest; res: ChartsUserReactionsResponse };
'charts/users': { req: ChartsUsersRequest; res: ChartsUsersResponse }; 'charts/users': { req: ChartsUsersRequest; res: ChartsUsersResponse };
'chat/history': { req: ChatHistoryRequest; res: ChatHistoryResponse }; '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/delete': { req: ChatMessagesDeleteRequest; res: ChatMessagesDeleteResponse };
'chat/messages/room-timeline': { req: ChatMessagesRoomTimelineRequest; res: ChatMessagesRoomTimelineResponse };
'chat/messages/show': { req: ChatMessagesShowRequest; res: ChatMessagesShowResponse }; '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/create': { req: ChatRoomsCreateRequest; res: ChatRoomsCreateResponse };
'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: ChatRoomsDeleteResponse }; 'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: ChatRoomsDeleteResponse };
'chat/rooms/invitations/create': { req: ChatRoomsInvitationsCreateRequest; res: ChatRoomsInvitationsCreateResponse }; '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/inbox': { req: ChatRoomsInvitationsInboxRequest; res: ChatRoomsInvitationsInboxResponse };
'chat/rooms/invitations/reject': { req: ChatRoomsInvitationsRejectRequest; res: ChatRoomsInvitationsRejectResponse };
'chat/rooms/join': { req: ChatRoomsJoinRequest; res: ChatRoomsJoinResponse }; 'chat/rooms/join': { req: ChatRoomsJoinRequest; res: ChatRoomsJoinResponse };
'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse }; 'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse };
'chat/rooms/members': { req: ChatRoomsMembersRequest; res: ChatRoomsMembersResponse }; 'chat/rooms/members': { req: ChatRoomsMembersRequest; res: ChatRoomsMembersResponse };

View File

@@ -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 ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json'];
export type ChatHistoryRequest = operations['chat___history']['requestBody']['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 ChatHistoryResponse = operations['chat___history']['responses']['200']['content']['application/json'];
export type ChatMessagesCreateRequest = operations['chat___messages___create']['requestBody']['content']['application/json']; export type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
export type ChatMessagesCreateResponse = operations['chat___messages___create']['responses']['200']['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 ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
export type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['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 ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
export type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['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 ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
export type ChatMessagesTimelineResponse = operations['chat___messages___timeline']['responses']['200']['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 ChatRoomsCreateRequest = operations['chat___rooms___create']['requestBody']['content']['application/json'];
export type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses']['200']['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 ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
export type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['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 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 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 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 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 ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
export type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['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']; export type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];

View File

@@ -1367,14 +1367,23 @@ export type paths = {
*/ */
post: operations['chat___history']; post: operations['chat___history'];
}; };
'/chat/messages/create': { '/chat/messages/create-to-room': {
/** /**
* chat/messages/create * chat/messages/create-to-room
* @description No description provided. * @description No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *write:chat* * **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': { '/chat/messages/delete': {
/** /**
@@ -1385,6 +1394,15 @@ export type paths = {
*/ */
post: operations['chat___messages___delete']; 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': {
/** /**
* chat/messages/show * chat/messages/show
@@ -1394,14 +1412,14 @@ export type paths = {
*/ */
post: operations['chat___messages___show']; post: operations['chat___messages___show'];
}; };
'/chat/messages/timeline': { '/chat/messages/user-timeline': {
/** /**
* chat/messages/timeline * chat/messages/user-timeline
* @description No description provided. * @description No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *read:chat* * **Credential required**: *Yes* / **Permission**: *read:chat*
*/ */
post: operations['chat___messages___timeline']; post: operations['chat___messages___user-timeline'];
}; };
'/chat/rooms/create': { '/chat/rooms/create': {
/** /**
@@ -1430,6 +1448,15 @@ export type paths = {
*/ */
post: operations['chat___rooms___invitations___create']; 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': {
/** /**
* chat/rooms/invitations/inbox * chat/rooms/invitations/inbox
@@ -1439,15 +1466,6 @@ export type paths = {
*/ */
post: operations['chat___rooms___invitations___inbox']; 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': {
/** /**
* 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. * @description No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *write:chat* * **Credential required**: *Yes* / **Permission**: *write:chat*
*/ */
chat___messages___create: { 'chat___messages___create-to-room': {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': {
@@ -13928,7 +13946,70 @@ export type operations = {
/** Format: misskey:id */ /** Format: misskey:id */
fileId?: string; fileId?: string;
/** Format: misskey:id */ /** 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 * chat/messages/show
* @description No description provided. * @description No description provided.
@@ -14086,12 +14227,12 @@ export type operations = {
}; };
}; };
/** /**
* chat/messages/timeline * chat/messages/user-timeline
* @description No description provided. * @description No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *read:chat* * **Credential required**: *Yes* / **Permission**: *read:chat*
*/ */
chat___messages___timeline: { 'chat___messages___user-timeline': {
requestBody: { requestBody: {
content: { content: {
'application/json': { 'application/json': {
@@ -14102,7 +14243,7 @@ export type operations = {
/** Format: misskey:id */ /** Format: misskey:id */
untilId?: string; untilId?: string;
/** Format: misskey:id */ /** 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. * @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: { requestBody: {
content: { content: {
'application/json': { 'application/json': {
/** @default 30 */
limit?: number;
/** Format: misskey:id */ /** Format: misskey:id */
sinceId?: string; roomId: string;
/** Format: misskey:id */
untilId?: string;
}; };
}; };
}; };
@@ -14343,7 +14480,7 @@ export type operations = {
/** @description OK (with results) */ /** @description OK (with results) */
200: { 200: {
content: { content: {
'application/json': components['schemas']['ChatRoomInvitation'][]; 'application/json': unknown;
}; };
}; };
/** @description Client error */ /** @description Client error */
@@ -14379,17 +14516,21 @@ export type operations = {
}; };
}; };
/** /**
* chat/rooms/invitations/reject * chat/rooms/invitations/inbox
* @description No description provided. * @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: { requestBody: {
content: { content: {
'application/json': { 'application/json': {
/** @default 30 */
limit?: number;
/** Format: misskey:id */ /** Format: misskey:id */
roomId: string; sinceId?: string;
/** Format: misskey:id */
untilId?: string;
}; };
}; };
}; };
@@ -14397,7 +14538,7 @@ export type operations = {
/** @description OK (with results) */ /** @description OK (with results) */
200: { 200: {
content: { content: {
'application/json': unknown; 'application/json': components['schemas']['ChatRoomInvitation'][];
}; };
}; };
/** @description Client error */ /** @description Client error */