import { Notes } from "../../../../models/index.js"; import { MAX_NOTE_TEXT_LENGTH } from "../../../../const.js"; import { noteVisibilities } from "../../../../types.js"; import { ApiError } from "../../error.js"; import define from "../../define.js"; import { HOUR } from "../../../../const.js"; import { createNoteFromApiData } from "../../../../services/note/create-from-api.js"; import { scheduleNote } from "../../../../services/note/scheduled.js"; import { enqueueScheduledNote } from "../../../../queue/queues/system/scheduled-note.js"; export const meta = { tags: [ "notes" ], requireCredential: true, limit: { duration: HOUR, max: 300 }, kind: "write:notes", res: { type: "object", optional: false, nullable: false, properties: { createdNote: { type: "object", optional: true, nullable: false, ref: "Note" }, scheduledNote: { type: "object", optional: true, nullable: false, properties: { id: { type: "string", optional: false, nullable: false }, scheduledAt: { type: "string", optional: false, nullable: false } } } } }, 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" }, 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" }, noSuchChannel: { message: "No such channel.", code: "NO_SUCH_CHANNEL", id: "b1653923-5453-4edc-b786-7c4f39bb0bbb" }, youHaveBeenBlocked: { message: "You have been blocked by this user.", code: "YOU_HAVE_BEEN_BLOCKED", id: "b390d7e1-8a5e-46ed-b625-06271cafd3d3" }, accountLocked: { message: "You migrated. Your account is now locked.", code: "ACCOUNT_LOCKED", id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3" }, noSuchGroup: { message: "No such group.", code: "NO_SUCH_GROUP", id: "7a42ce9c-bdbd-4dcc-8a5d-daf2f520bfaa" }, cannotScheduleToPast: { message: "Scheduled time must be in the future.", code: "CANNOT_SCHEDULE_TO_PAST", id: "181f385d-1f59-42c8-b3a2-b52253a92057" } } }; export const paramDef = { type: "object", properties: { visibility: { type: "string", enum: noteVisibilities, default: "public" }, visibleUserIds: { type: "array", uniqueItems: true, items: { type: "string", format: "misskey:id" } }, text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, cw: { type: "string", nullable: true, maxLength: 100 }, localOnly: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false }, noExtractHashtags: { type: "boolean", default: false }, noExtractEmojis: { type: "boolean", default: false }, fileIds: { type: "array", uniqueItems: true, minItems: 1, maxItems: 16, items: { type: "string", format: "misskey:id" } }, mediaIds: { deprecated: true, description: "Use `fileIds` instead. If both are specified, this property is discarded.", type: "array", uniqueItems: true, minItems: 1, maxItems: 16, items: { type: "string", format: "misskey:id" } }, replyId: { type: "string", format: "misskey:id", nullable: true }, renoteId: { type: "string", format: "misskey:id", nullable: true }, channelId: { type: "string", format: "misskey:id", nullable: true }, groupId: { type: "string", format: "misskey:id", nullable: true }, scheduledAt: { type: "integer", nullable: true }, 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", default: false }, expiresAt: { type: "integer", nullable: true }, expiredAfter: { type: "integer", nullable: true, minimum: 1 } }, required: [ "choices" ] } }, anyOf: [ { // (re)note with text, files and poll are optional properties: { text: { type: "string", minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false } }, required: [ "text" ] }, { // (re)note with files, text and poll are optional required: [ "fileIds" ] }, { // (re)note with files, text and poll are optional required: [ "mediaIds" ] }, { // (re)note with poll, text and files are optional properties: { poll: { type: "object", nullable: false } }, required: [ "poll" ] }, { // pure renote required: [ "renoteId" ] } ] }; export default define(meta, paramDef, async (ps, user)=>{ if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); if (typeof ps.scheduledAt === "number") { const scheduledAt = new Date(ps.scheduledAt); if (scheduledAt.getTime() <= Date.now()) { throw new ApiError(meta.errors.cannotScheduleToPast); } const scheduledNote = await scheduleNote(user, ps, scheduledAt); await enqueueScheduledNote(scheduledNote).catch((err)=>{ console.error("Failed to enqueue scheduled note", err); }); return { scheduledNote: { id: scheduledNote.id, scheduledAt: scheduledNote.scheduledAt.toISOString() } }; } const note = await createNoteFromApiData(user, ps, new Date()); return { createdNote: await Notes.pack(note, user) }; });