diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-09-26 15:29:52 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-26 15:29:52 +0900 |
| commit | d1446d195abb52c560c7c97177d08103a175acf7 (patch) | |
| tree | 355689adb542333f4c5abb186ab9819c29274612 /packages/backend/src/server | |
| parent | fix(frontend): ビルド成果物のファイル名にlocalesのhashを含め... (diff) | |
| download | misskey-d1446d195abb52c560c7c97177d08103a175acf7.tar.gz misskey-d1446d195abb52c560c7c97177d08103a175acf7.tar.bz2 misskey-d1446d195abb52c560c7c97177d08103a175acf7.zip | |
feat: scheduled post (#16577)
* Update NoteDraft.ts
* Update NoteDraft.ts
* wip
* Update CHANGELOG.md
* wip
* Update PostScheduledNoteProcessorService.ts
* Update PostScheduledNoteProcessorService.ts
* Update Notification.ts
* wip
* Update NoteDraftService.ts
* Update NoteDraftService.ts
* Update NoteDraftService.ts
* wip
* Create 1758677617888-scheduled-post.js
* Update index.d.ts
* Update stats.ts
* wip
* wip
* wip
* wip
* wip
* Update MkNotification.vue
* wip
* wip
* wip
* Update NoteDraftService.ts
* Update NoteDraftService.ts
* wip
* wip
* Update NoteDraftEntityService.ts
* wip
* Update index.d.ts
* Update MkPostForm.vue
* wip
* wip
* wip
* Update NoteCreateService.ts
* wip
* wip
* wip
* Update NoteDraftEntityService.ts
* Update NoteCreateService.ts
* Update NoteDraftService.ts
* wip
* Update NoteDraftService.ts
* wip
* wip
* Update MkPostForm.vue
* wip
* Update MkPostForm.vue
* Update os.ts
* wip
* Update MkNoteDraftsDialog.vue
Diffstat (limited to 'packages/backend/src/server')
7 files changed, 111 insertions, 195 deletions
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index d7f9e4eaa3..b69699c338 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js'; +import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, PostScheduledNoteQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js'; export const meta = { tags: ['admin'], @@ -49,6 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- constructor( @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, + @Inject('queue:postScheduledNote') public postScheduledNoteQueue: PostScheduledNoteQueue, @Inject('queue:deliver') public deliverQueue: DeliverQueue, @Inject('queue:inbox') public inboxQueue: InboxQueue, @Inject('queue:db') public dbQueue: DbQueue, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 1ba6853dbe..2fd7ab8ca2 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -103,6 +103,8 @@ export const meta = { quote: { optional: true, ...notificationRecieveConfig }, reaction: { optional: true, ...notificationRecieveConfig }, pollEnded: { optional: true, ...notificationRecieveConfig }, + scheduledNotePosted: { optional: true, ...notificationRecieveConfig }, + scheduledNotePostFailed: { optional: true, ...notificationRecieveConfig }, receiveFollowRequest: { optional: true, ...notificationRecieveConfig }, followRequestAccepted: { optional: true, ...notificationRecieveConfig }, roleAssigned: { optional: true, ...notificationRecieveConfig }, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 082d97f5d4..5c7958fc1c 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -209,6 +209,8 @@ export const paramDef = { quote: notificationRecieveConfig, reaction: notificationRecieveConfig, pollEnded: notificationRecieveConfig, + scheduledNotePosted: notificationRecieveConfig, + scheduledNotePostFailed: notificationRecieveConfig, receiveFollowRequest: notificationRecieveConfig, followRequestAccepted: notificationRecieveConfig, roleAssigned: notificationRecieveConfig, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 7caea8eedc..e48aa69d0f 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -6,17 +6,10 @@ import ms from 'ms'; import { In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { MiUser } from '@/models/User.js'; -import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiNote } from '@/models/Note.js'; -import type { MiChannel } from '@/models/Channel.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 { NoteCreateService } from '@/core/NoteCreateService.js'; -import { DI } from '@/di-symbols.js'; -import { isQuote, isRenote } from '@/misc/is-renote.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; @@ -223,168 +216,28 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.channelsRepository) - private channelsRepository: ChannelsRepository, - private noteEntityService: NoteEntityService, private noteCreateService: NoteCreateService, ) { 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.findOne({ - where: { id: ps.renoteId }, - relations: ['user', 'renote', 'reply'], - }); - - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (isRenote(renote) && !isQuote(renote)) { - throw new ApiError(meta.errors.cannotReRenote); - } - - // Check blocking - if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - 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.findOneBy({ id: 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.findOne({ - where: { id: ps.replyId }, - relations: ['user'], - }); - - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (isRenote(reply) && !isQuote(reply)) { - throw new ApiError(meta.errors.cannotReplyToPureRenote); - } else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) { - throw new ApiError(meta.errors.cannotReplyToInvisibleNote); - } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') { - throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); - } - - // Check blocking - if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exists({ - 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); - } - } - - // 投稿を作成 try { - const note = await this.noteCreateService.create(me, { + const note = await this.noteCreateService.fetchAndCreate(me, { createdAt: new Date(), - files: files, + fileIds: ps.fileIds ?? ps.mediaIds ?? [], 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 ?? undefined, - reply, - renote, - cw: ps.cw, + expiresAt: ps.poll.expiredAfter ? new Date(Date.now() + ps.poll.expiredAfter) : ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } : null, + text: ps.text ?? null, + replyId: ps.replyId ?? null, + renoteId: ps.renoteId ?? null, + cw: ps.cw ?? null, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUsers, - channel, + visibleUserIds: ps.visibleUserIds ?? [], + channelId: ps.channelId ?? null, apMentions: ps.noExtractMentions ? [] : undefined, apHashtags: ps.noExtractHashtags ? [] : undefined, apEmojis: ps.noExtractEmojis ? [] : undefined, @@ -393,16 +246,46 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- return { createdNote: await this.noteEntityService.pack(note, me), }; - } catch (e) { + } catch (err) { // TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい - if (e instanceof IdentifiableError) { - if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { + if (err instanceof IdentifiableError) { + if (err.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { throw new ApiError(meta.errors.containsProhibitedWords); - } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { + } else if (err.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { throw new ApiError(meta.errors.containsTooManyMentions); + } else if (err.id === '801c046c-5bf5-4234-ad2b-e78fc20a2ac7') { + throw new ApiError(meta.errors.noSuchFile); + } else if (err.id === '53983c56-e163-45a6-942f-4ddc485d4290') { + throw new ApiError(meta.errors.noSuchRenoteTarget); + } else if (err.id === 'bde24c37-121f-4e7d-980d-cec52f599f02') { + throw new ApiError(meta.errors.cannotReRenote); + } else if (err.id === '2b4fe776-4414-4a2d-ae39-f3418b8fd4d3') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (err.id === '90b9d6f0-893a-4fef-b0f1-e9a33989f71a') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (err.id === '48d7a997-da5c-4716-b3c3-92db3f37bf7d') { + throw new ApiError(meta.errors.cannotRenoteDueToVisibility); + } else if (err.id === 'b060f9a6-8909-4080-9e0b-94d9fa6f6a77') { + throw new ApiError(meta.errors.noSuchChannel); + } else if (err.id === '7e435f4a-780d-4cfc-a15a-42519bd6fb67') { + throw new ApiError(meta.errors.cannotRenoteOutsideOfChannel); + } else if (err.id === '60142edb-1519-408e-926d-4f108d27bee0') { + throw new ApiError(meta.errors.noSuchReplyTarget); + } else if (err.id === 'f089e4e2-c0e7-4f60-8a23-e5a6bf786b36') { + throw new ApiError(meta.errors.cannotReplyToPureRenote); + } else if (err.id === '11cd37b3-a411-4f77-8633-c580ce6a8dce') { + throw new ApiError(meta.errors.cannotReplyToInvisibleNote); + } else if (err.id === 'ced780a1-2012-4caf-bc7e-a95a291294cb') { + throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); + } else if (err.id === 'b0df6025-f2e8-44b4-a26a-17ad99104612') { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } else if (err.id === '0c11c11e-0c8d-48e7-822c-76ccef660068') { + throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); + } else if (err.id === 'bfa3905b-25f5-4894-b430-da331a490e4b') { + throw new ApiError(meta.errors.noSuchChannel); } } - throw e; + throw err; } }); } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts index 1c28ec22d0..8f2fbf9197 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts @@ -124,6 +124,12 @@ export const meta = { id: '9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', }, + tooManyScheduledNotes: { + message: 'You cannot create scheduled notes any more.', + code: 'TOO_MANY_SCHEDULED_NOTES', + id: '22ae69eb-09e3-4541-a850-773cfa45e693', + }, + cannotRenoteToExternal: { message: 'Cannot Renote to External.', code: 'CANNOT_RENOTE_TO_EXTERNAL', @@ -162,7 +168,7 @@ export const paramDef = { fileIds: { type: 'array', uniqueItems: true, - minItems: 1, + minItems: 0, maxItems: 16, items: { type: 'string', format: 'misskey:id' }, }, @@ -183,8 +189,10 @@ export const paramDef = { }, required: ['choices'], }, + scheduledAt: { type: 'integer', nullable: true }, + isActuallyScheduled: { type: 'boolean', default: false }, }, - required: [], + required: ['visibility', 'visibleUserIds', 'cw', 'hashtag', 'localOnly', 'reactionAcceptance', 'replyId', 'renoteId', 'channelId', 'text', 'fileIds', 'poll', 'scheduledAt', 'isActuallyScheduled'], } as const; @Injectable() @@ -196,22 +204,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- super(meta, paramDef, async (ps, me) => { const draft = await this.noteDraftService.create(me, { fileIds: ps.fileIds, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple ?? false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - expiredAfter: ps.poll.expiredAfter ?? null, - } : undefined, - text: ps.text ?? null, - replyId: ps.replyId ?? undefined, - renoteId: ps.renoteId ?? undefined, - cw: ps.cw ?? null, - ...(ps.hashtag ? { hashtag: ps.hashtag } : {}), + pollChoices: ps.poll?.choices ?? [], + pollMultiple: ps.poll?.multiple ?? false, + pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null, + pollExpiredAfter: ps.poll?.expiredAfter ?? null, + hasPoll: ps.poll != null, + text: ps.text, + replyId: ps.replyId, + renoteId: ps.renoteId, + cw: ps.cw, + hashtag: ps.hashtag, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUserIds: ps.visibleUserIds ?? [], - channelId: ps.channelId ?? undefined, + visibleUserIds: ps.visibleUserIds, + channelId: ps.channelId, + scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, + isActuallyScheduled: ps.isActuallyScheduled, }).catch((err) => { if (err instanceof IdentifiableError) { switch (err.id) { @@ -241,6 +250,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.cannotReplyToInvisibleNote); case '215dbc76-336c-4d2a-9605-95766ba7dab0': throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); + case 'c3275f19-4558-4c59-83e1-4f684b5fab66': + throw new ApiError(meta.errors.tooManyScheduledNotes); default: throw err; } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/list.ts b/packages/backend/src/server/api/endpoints/notes/drafts/list.ts index f24f9b8fb2..0774f09228 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/list.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/list.ts @@ -41,6 +41,7 @@ export const paramDef = { untilId: { type: 'string', format: 'misskey:id' }, sinceDate: { type: 'integer' }, untilDate: { type: 'integer' }, + scheduled: { type: 'boolean', nullable: true }, }, required: [], } as const; @@ -58,6 +59,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- const query = this.queryService.makePaginationQuery<MiNoteDraft>(this.noteDraftsRepository.createQueryBuilder('drafts'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('drafts.userId = :meId', { meId: me.id }); + if (ps.scheduled === true) { + query.andWhere('drafts.isActuallyScheduled = true'); + } else if (ps.scheduled === false) { + query.andWhere('drafts.isActuallyScheduled = false'); + } + const drafts = await query .limit(ps.limit) .getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts index ee221fb765..9a2e2ca415 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts @@ -159,6 +159,12 @@ export const meta = { code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', id: '215dbc76-336c-4d2a-9605-95766ba7dab0', }, + + tooManyScheduledNotes: { + message: 'You cannot create scheduled notes any more.', + code: 'TOO_MANY_SCHEDULED_NOTES', + id: '02f5df79-08ae-4a33-8524-f1503c8f6212', + }, }, limit: { @@ -171,14 +177,14 @@ export const paramDef = { type: 'object', properties: { draftId: { type: 'string', nullable: false, format: 'misskey:id' }, - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'] }, visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 }, hashtag: { type: 'string', nullable: true, maxLength: 200 }, - localOnly: { type: 'boolean', default: false }, - reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, + localOnly: { type: 'boolean' }, + reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'] }, replyId: { type: 'string', format: 'misskey:id', nullable: true }, renoteId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true }, @@ -194,7 +200,7 @@ export const paramDef = { fileIds: { type: 'array', uniqueItems: true, - minItems: 1, + minItems: 0, maxItems: 16, items: { type: 'string', format: 'misskey:id' }, }, @@ -215,6 +221,8 @@ export const paramDef = { }, required: ['choices'], }, + scheduledAt: { type: 'integer', nullable: true }, + isActuallyScheduled: { type: 'boolean' }, }, required: ['draftId'], } as const; @@ -228,22 +236,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- super(meta, paramDef, async (ps, me) => { const draft = await this.noteDraftService.update(me, ps.draftId, { fileIds: ps.fileIds, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple ?? false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - expiredAfter: ps.poll.expiredAfter ?? null, - } : undefined, - text: ps.text ?? null, - replyId: ps.replyId ?? undefined, - renoteId: ps.renoteId ?? undefined, - cw: ps.cw ?? null, - ...(ps.hashtag ? { hashtag: ps.hashtag } : {}), + pollChoices: ps.poll?.choices, + pollMultiple: ps.poll?.multiple, + pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null, + pollExpiredAfter: ps.poll?.expiredAfter, + text: ps.text, + replyId: ps.replyId, + renoteId: ps.renoteId, + cw: ps.cw, + hashtag: ps.hashtag, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUserIds: ps.visibleUserIds ?? [], - channelId: ps.channelId ?? undefined, + visibleUserIds: ps.visibleUserIds, + channelId: ps.channelId, + scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, + isActuallyScheduled: ps.isActuallyScheduled, }).catch((err) => { if (err instanceof IdentifiableError) { switch (err.id) { @@ -285,6 +293,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.containsProhibitedWords); case '4de0363a-3046-481b-9b0f-feff3e211025': throw new ApiError(meta.errors.containsTooManyMentions); + case 'bacdf856-5c51-4159-b88a-804fa5103be5': + throw new ApiError(meta.errors.tooManyScheduledNotes); default: throw err; } |