diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index cb9a17fdb6..09126c46da 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -263,7 +263,6 @@ import * as ep___notes_children from './endpoints/notes/children.js'; import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; -import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js'; import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js'; import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; @@ -624,7 +623,6 @@ const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep__ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default }; const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; -const $notes_schedule_create: Provider = { provide: 'ep:notes/schedule/create', useClass: ep___notes_schedule_create.default }; const $notes_schedule_delete: Provider = { provide: 'ep:notes/schedule/delete', useClass: ep___notes_schedule_delete.default }; const $notes_schedule_list: Provider = { provide: 'ep:notes/schedule/list', useClass: ep___notes_schedule_list.default }; const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; @@ -989,7 +987,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_clips, $notes_conversation, $notes_create, - $notes_schedule_create, $notes_schedule_delete, $notes_schedule_list, $notes_delete, @@ -1348,7 +1345,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_clips, $notes_conversation, $notes_create, - $notes_schedule_create, $notes_schedule_delete, $notes_schedule_list, $notes_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 6010589093..6a98fb21ed 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -263,7 +263,6 @@ import * as ep___notes_children from './endpoints/notes/children.js'; import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; -import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js'; import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js'; import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; @@ -622,7 +621,6 @@ const eps = [ ['notes/clips', ep___notes_clips], ['notes/conversation', ep___notes_conversation], ['notes/create', ep___notes_create], - ['notes/schedule/create', ep___notes_schedule_create], ['notes/schedule/delete', ep___notes_schedule_delete], ['notes/schedule/list', ep___notes_schedule_list], ['notes/delete', ep___notes_delete], diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts deleted file mode 100644 index 44deb7bb9b..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts +++ /dev/null @@ -1,393 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import ms from 'ms'; -import { DataSource, In } from 'typeorm'; -import { Inject, Injectable } from '@nestjs/common'; -import type { MiUser } from '@/models/User.js'; -import type { MiNote } from '@/models/Note.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiChannel } from '@/models/Channel.js'; -import type { - UsersRepository, - NotesRepository, - BlockingsRepository, - DriveFilesRepository, - ChannelsRepository, - ScheduledNotesRepository, -} from '@/models/_.js'; -import type { MiNoteCreateOption } from '@/types.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { DI } from '@/di-symbols.js'; -import { isPureRenote } from '@/misc/is-pure-renote.js'; -import { QueueService } from '@/core/QueueService.js'; -import { IdService } from '@/core/IdService.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - - prohibitMoved: true, - - limit: { - duration: ms('1hour'), - max: 300, - }, - - kind: 'write:notes', - - errors: { - noSuchRenoteTarget: { - message: 'No such renote target.', - code: 'NO_SUCH_RENOTE_TARGET', - id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4', - }, - - cannotReRenote: { - message: 'You can not Renote a pure Renote.', - code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE', - id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a', - }, - - cannotRenoteDueToVisibility: { - message: 'You can not Renote due to target visibility.', - code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY', - id: 'be9529e9-fe72-4de0-ae43-0b363c4938af', - }, - - noSuchReplyTarget: { - message: 'No such reply target.', - code: 'NO_SUCH_REPLY_TARGET', - id: '749ee0f6-d3da-459a-bf02-282e2da4292c', - }, - - cannotReplyToPureRenote: { - message: 'You can not reply to a pure Renote.', - code: 'CANNOT_REPLY_TO_A_PURE_RENOTE', - id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', - }, - - cannotCreateAlreadyExpiredPoll: { - message: 'Poll is already expired.', - code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', - id: '04da457d-b083-4055-9082-955525eda5a5', - }, - - cannotCreateAlreadyExpiredSchedule: { - message: 'Schedule is already expired.', - code: 'CANNOT_CREATE_ALREADY_EXPIRED_SCHEDULE', - id: '8a9bfb90-fc7e-4878-a3e8-d97faaf5fb07', - }, - - specifyScheduleDate: { - message: 'Please specify schedule date.', - code: 'PLEASE_SPECIFY_SCHEDULE_DATE', - id: 'c93a6ad6-f7e2-4156-a0c2-3d03529e5e0f', - }, - - noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb', - }, - noSuchSchedule: { - message: 'No such schedule.', - code: 'NO_SUCH_SCHEDULE', - id: '44dee229-8da1-4a61-856d-e3a4bbc12032', - }, - youHaveBeenBlocked: { - message: 'You have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', - }, - - noSuchFile: { - message: 'Some files are not found.', - code: 'NO_SUCH_FILE', - id: 'b6992544-63e7-67f0-fa7f-32444b1b5306', - }, - - cannotRenoteOutsideOfChannel: { - message: 'Cannot renote outside of channel.', - code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL', - id: '33510210-8452-094c-6227-4a6c05d99f00', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, - visibleUserIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 }, - localOnly: { type: 'boolean', default: false }, - reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, - noExtractMentions: { type: 'boolean', default: false }, - noExtractHashtags: { type: 'boolean', default: false }, - noExtractEmojis: { type: 'boolean', default: false }, - replyId: { type: 'string', format: 'misskey:id', nullable: true }, - renoteId: { type: 'string', format: 'misskey:id', nullable: true }, - channelId: { type: 'string', format: 'misskey:id', nullable: true }, - - // anyOf内にバリデーションを書いても最初の一つしかチェックされない - // See https://github.com/misskey-dev/misskey/pull/10082 - text: { - type: 'string', - minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, - nullable: true, - }, - fileIds: { - type: 'array', - uniqueItems: true, - minItems: 1, - maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, - }, - mediaIds: { - type: 'array', - uniqueItems: true, - minItems: 1, - maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, - }, - poll: { - type: 'object', - nullable: true, - properties: { - choices: { - type: 'array', - uniqueItems: true, - minItems: 2, - maxItems: 10, - items: { type: 'string', minLength: 1, maxLength: 50 }, - }, - multiple: { type: 'boolean' }, - expiresAt: { type: 'integer', nullable: true }, - expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, - }, - required: ['choices'], - }, - schedule: { - type: 'object', - nullable: false, - properties: { - expiresAt: { type: 'integer', nullable: false }, - }, - }, - }, - // (re)note with text, files and poll are optional - anyOf: [ - { required: ['text'] }, - { required: ['renoteId'] }, - { required: ['fileIds'] }, - { required: ['mediaIds'] }, - { required: ['poll'] }, - { required: ['schedule'] }, - ], -} as const; - -@Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.scheduledNotesRepository) - private scheduledNotesRepository: ScheduledNotesRepository, - - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.channelsRepository) - private channelsRepository: ChannelsRepository, - - private noteEntityService: NoteEntityService, - private queueService: QueueService, - private idService: IdService, - ) { - super(meta, paramDef, async (ps, me) => { - let visibleUsers: MiUser[] = []; - if (ps.visibleUserIds) { - visibleUsers = await this.usersRepository.findBy({ - id: In(ps.visibleUserIds), - }); - } - - let files: MiDriveFile[] = []; - const fileIds = ps.fileIds ?? ps.mediaIds ?? null; - if (fileIds != null) { - files = await this.driveFilesRepository.createQueryBuilder('file') - .where('file.userId = :userId AND file.id IN (:...fileIds)', { - userId: me.id, - fileIds, - }) - .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') - .setParameters({ fileIds }) - .getMany(); - - if (files.length !== fileIds.length) { - throw new ApiError(meta.errors.noSuchFile); - } - } - - let renote: MiNote | null = null; - if (ps.renoteId != null) { - // Fetch renote to note - renote = await this.notesRepository.findOneBy({ id: ps.renoteId }); - - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (isPureRenote(renote)) { - throw new ApiError(meta.errors.cannotReRenote); - } - - // Check blocking - if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ - where: { - blockerId: renote.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - - if (renote.visibility === 'followers' && renote.userId !== me.id) { - // 他人のfollowers noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } else if (renote.visibility === 'specified') { - // specified / direct noteはreject - throw new ApiError(meta.errors.cannotRenoteDueToVisibility); - } - - if (renote.channelId && renote.channelId !== ps.channelId) { - // チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック - // リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する - const renoteChannel = await this.channelsRepository.findOneById(renote.channelId); - if (renoteChannel == null) { - // リノートしたいノートが書き込まれているチャンネルが無い - throw new ApiError(meta.errors.noSuchChannel); - } else if (!renoteChannel.allowRenoteToExternal) { - // リノート作成のリクエストだが、対象チャンネルがリノート禁止だった場合 - throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel); - } - } - } - - let reply: MiNote | null = null; - if (ps.replyId != null) { - // Fetch reply - reply = await this.notesRepository.findOneBy({ id: ps.replyId }); - - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isPureRenote(reply)) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } - - // Check blocking - if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ - where: { - blockerId: reply.userId, - blockeeId: me.id, - }, - }); - if (blockExist) { - throw new ApiError(meta.errors.youHaveBeenBlocked); - } - } - } - - if (ps.poll) { - if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { - throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); - } - } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; - } - } - - let channel: MiChannel | null = null; - if (ps.channelId != null) { - channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false }); - - if (channel == null) { - throw new ApiError(meta.errors.noSuchChannel); - } - } - if (ps.schedule) { - if (typeof ps.schedule.expiresAt === 'number') { - if (ps.schedule.expiresAt < Date.now()) { - throw new ApiError(meta.errors.cannotCreateAlreadyExpiredSchedule); - } - } - } - const note: MiNoteCreateOption = { - files, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple ?? false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - } : undefined, - text: ps.text ?? null, - reply, - renote, - cw: ps.cw ?? null, - localOnly: ps.localOnly, - reactionAcceptance: ps.reactionAcceptance, - visibility: ps.visibility, - visibleUsers, - channel, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, - }; - - if (ps.schedule && ps.schedule.expiresAt) { - me.token = null; - const scheduleNoteId = this.idService.gen(new Date().getTime()); - await this.scheduledNotesRepository.insert({ - id: scheduleNoteId, - note: note, - userId: me.id, - expiresAt: new Date(ps.schedule.expiresAt), - }); - - const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); - await this.queueService.ScheduleNotePostQueue.add(String(delay), { - scheduleNoteId, - }, { - delay, - removeOnComplete: true, - }); - - return { - scheduleNoteId, - scheduledNote: note, - }; - } else { - throw new ApiError(meta.errors.specifyScheduleDate); - } - }); - } -} diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 6319c49ebe..fa5faadcbc 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1762,16 +1762,6 @@ export type Endpoints = { }; res: null; }; - 'notes/schedule/create': { - req: Partial & { - schedule: { - expiresAt: number; - }; - }; - res: { - createdNote: Note; - }; - }; 'notes/schedule/delete': { req: { scheduledNoteId: Note['id']; @@ -3063,7 +3053,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:643:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts +// src/api.types.ts:642:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/entities.ts:116:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts // src/entities.ts:627:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index 12030be37e..f1a7179be9 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -512,7 +512,6 @@ export type Endpoints = { }; }; res: { createdNote: Note }; }; 'notes/delete': { req: { noteId: Note['id']; }; res: null; }; - 'notes/schedule/create': { req: Partial & { schedule: { expiresAt: number; } }; res: { createdNote: Note }; }; 'notes/schedule/delete': { req: { scheduledNoteId: Note['id']; }; res: null; }; 'notes/schedule/list': { req: TODO; res: { id: Note['id'];