Compare commits
	
		
			15 Commits
		
	
	
		
			2ab2081caa
			...
			b97f4a9407
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | b97f4a9407 | |
|  | f0c297c483 | |
|  | 9bd4dbc906 | |
|  | 4ac1dc8740 | |
|  | 6471863ba5 | |
|  | 45074de293 | |
|  | 5e766ae230 | |
|  | 5d2bc61ede | |
|  | 1fe2264f41 | |
|  | 2b24dd6075 | |
|  | 43f82593ab | |
|  | 41336d880f | |
|  | 2faa39f5ae | |
|  | 1df36d5fc7 | |
|  | 914eb0bd35 | 
|  | @ -5,6 +5,7 @@ | ||||||
| 
 | 
 | ||||||
| ### General | ### General | ||||||
| - Feat: 予約投稿ができるようになりました | - Feat: 予約投稿ができるようになりました | ||||||
|  | 	- デフォルトで作成可能数は1になっています。適宜ロールのポリシーで設定を行ってください。 | ||||||
| - Enhance: 広告ごとにセンシティブフラグを設定できるようになりました | - Enhance: 広告ごとにセンシティブフラグを設定できるようになりました | ||||||
| 
 | 
 | ||||||
| ### Client | ### Client | ||||||
|  |  | ||||||
|  | @ -7957,6 +7957,10 @@ export interface Locale extends ILocale { | ||||||
|              * サーバーサイドのノートの下書きの作成可能数 |              * サーバーサイドのノートの下書きの作成可能数 | ||||||
|              */ |              */ | ||||||
|             "noteDraftLimit": string; |             "noteDraftLimit": string; | ||||||
|  |             /** | ||||||
|  |              * 予約投稿の同時作成可能数 | ||||||
|  |              */ | ||||||
|  |             "scheduledNoteLimit": string; | ||||||
|             /** |             /** | ||||||
|              * ウォーターマーク機能の使用可否 |              * ウォーターマーク機能の使用可否 | ||||||
|              */ |              */ | ||||||
|  |  | ||||||
|  | @ -2062,6 +2062,7 @@ _role: | ||||||
|     uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)" |     uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)" | ||||||
|     uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。" |     uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。" | ||||||
|     noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数" |     noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数" | ||||||
|  |     scheduledNoteLimit: "予約投稿の同時作成可能数" | ||||||
|     watermarkAvailable: "ウォーターマーク機能の使用可否" |     watermarkAvailable: "ウォーターマーク機能の使用可否" | ||||||
|   _condition: |   _condition: | ||||||
|     roleAssignedTo: "マニュアルロールにアサイン済み" |     roleAssignedTo: "マニュアルロールにアサイン済み" | ||||||
|  |  | ||||||
|  | @ -236,24 +236,25 @@ export class NoteCreateService implements OnApplicationShutdown { | ||||||
| 		isBot: MiUser['isBot']; | 		isBot: MiUser['isBot']; | ||||||
| 		isCat: MiUser['isCat']; | 		isCat: MiUser['isCat']; | ||||||
| 	}, data: { | 	}, data: { | ||||||
|  | 		createdAt: Date; | ||||||
| 		replyId: MiNote['id'] | null; | 		replyId: MiNote['id'] | null; | ||||||
| 		renoteId: MiNote['id'] | null; | 		renoteId: MiNote['id'] | null; | ||||||
| 		fileIds: MiDriveFile['id'][]; | 		fileIds: MiDriveFile['id'][]; | ||||||
| 		text: string | null; | 		text: string | null; | ||||||
| 		cw: string | null; | 		cw: string | null; | ||||||
| 		visibility: string | null; | 		visibility: string; | ||||||
| 		visibleUserIds: MiUser['id'][]; | 		visibleUserIds: MiUser['id'][]; | ||||||
| 		channelId: MiChannel['id'] | null; | 		channelId: MiChannel['id'] | null; | ||||||
| 		localOnly: boolean | null; | 		localOnly: boolean; | ||||||
| 		reactionAcceptance: MiNote['reactionAcceptance']; | 		reactionAcceptance: MiNote['reactionAcceptance']; | ||||||
| 
 | 		poll: IPoll | null; | ||||||
|  | 		apMentions?: MinimumUser[] | null; | ||||||
|  | 		apHashtags?: string[] | null; | ||||||
|  | 		apEmojis?: string[] | null; | ||||||
| 	}): Promise<MiNote> { | 	}): Promise<MiNote> { | ||||||
| 		let visibleUsers: MiUser[] = []; | 		const visibleUsers = data.visibleUserIds.length > 0 ? await this.usersRepository.findBy({ | ||||||
| 		if (data.visibleUserIds) { | 			id: In(data.visibleUserIds), | ||||||
| 			visibleUsers = await this.usersRepository.findBy({ | 		}) : []; | ||||||
| 				id: In(data.visibleUserIds), |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		let files: MiDriveFile[] = []; | 		let files: MiDriveFile[] = []; | ||||||
| 		if (data.fileIds.length > 0) { | 		if (data.fileIds.length > 0) { | ||||||
|  | @ -352,13 +353,11 @@ export class NoteCreateService implements OnApplicationShutdown { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (ps.poll) { | 		if (data.poll) { | ||||||
| 			if (typeof ps.poll.expiresAt === 'number') { | 			if (data.poll.expiresAt != null) { | ||||||
| 				if (ps.poll.expiresAt < Date.now()) { | 				if (data.poll.expiresAt.getTime() < Date.now()) { | ||||||
| 					throw new IdentifiableError('0c11c11e-0c8d-48e7-822c-76ccef660068', 'Poll expiration must be future time'); | 					throw new IdentifiableError('0c11c11e-0c8d-48e7-822c-76ccef660068', 'Poll expiration must be future time'); | ||||||
| 				} | 				} | ||||||
| 			} else if (typeof ps.poll.expiredAfter === 'number') { |  | ||||||
| 				ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -372,14 +371,10 @@ export class NoteCreateService implements OnApplicationShutdown { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return this.create(user, { | 		return this.create(user, { | ||||||
| 			createdAt: new Date(), | 			createdAt: data.createdAt, | ||||||
| 			files: files, | 			files: files, | ||||||
| 			poll: ps.poll ? { | 			poll: data.poll, | ||||||
| 				choices: ps.poll.choices, | 			text: data.text, | ||||||
| 				multiple: ps.poll.multiple ?? false, |  | ||||||
| 				expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, |  | ||||||
| 			} : undefined, |  | ||||||
| 			text: data.text ?? undefined, |  | ||||||
| 			reply, | 			reply, | ||||||
| 			renote, | 			renote, | ||||||
| 			cw: data.cw, | 			cw: data.cw, | ||||||
|  | @ -388,9 +383,9 @@ export class NoteCreateService implements OnApplicationShutdown { | ||||||
| 			visibility: data.visibility, | 			visibility: data.visibility, | ||||||
| 			visibleUsers, | 			visibleUsers, | ||||||
| 			channel, | 			channel, | ||||||
| 			apMentions: data.noExtractMentions ? [] : undefined, | 			apMentions: data.apMentions, | ||||||
| 			apHashtags: data.noExtractHashtags ? [] : undefined, | 			apHashtags: data.apHashtags, | ||||||
| 			apEmojis: data.noExtractEmojis ? [] : undefined, | 			apEmojis: data.apEmojis, | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,14 +5,12 @@ | ||||||
| 
 | 
 | ||||||
| import { Inject, Injectable } from '@nestjs/common'; | import { Inject, Injectable } from '@nestjs/common'; | ||||||
| import { In } from 'typeorm'; | import { In } from 'typeorm'; | ||||||
| import type { noteVisibilities, noteReactionAcceptances } from '@/types.js'; |  | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import type { MiNoteDraft, NoteDraftsRepository, MiNote, MiDriveFile, MiChannel, UsersRepository, DriveFilesRepository, NotesRepository, BlockingsRepository, ChannelsRepository } from '@/models/_.js'; | import type { MiNoteDraft, NoteDraftsRepository, MiNote, MiDriveFile, MiChannel, UsersRepository, DriveFilesRepository, NotesRepository, BlockingsRepository, ChannelsRepository } from '@/models/_.js'; | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
| import { RoleService } from '@/core/RoleService.js'; | import { RoleService } from '@/core/RoleService.js'; | ||||||
| import { IdService } from '@/core/IdService.js'; | import { IdService } from '@/core/IdService.js'; | ||||||
| import type { MiLocalUser, MiUser } from '@/models/User.js'; | import type { MiLocalUser, MiUser } from '@/models/User.js'; | ||||||
| import { IPoll } from '@/models/Poll.js'; |  | ||||||
| import { IdentifiableError } from '@/misc/identifiable-error.js'; | import { IdentifiableError } from '@/misc/identifiable-error.js'; | ||||||
| import { isRenote, isQuote } from '@/misc/is-renote.js'; | import { isRenote, isQuote } from '@/misc/is-renote.js'; | ||||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||||
|  | @ -61,12 +59,24 @@ export class NoteDraftService { | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async create(me: MiLocalUser, data: NoteDraftOptions): Promise<MiNoteDraft> { | 	public async create(me: MiLocalUser, data: NoteDraftOptions): Promise<MiNoteDraft> { | ||||||
| 		//#region check draft limit
 | 		//#region check draft limit
 | ||||||
|  | 		const policies = await this.roleService.getUserPolicies(me.id); | ||||||
|  | 
 | ||||||
| 		const currentCount = await this.noteDraftsRepository.countBy({ | 		const currentCount = await this.noteDraftsRepository.countBy({ | ||||||
| 			userId: me.id, | 			userId: me.id, | ||||||
| 		}); | 		}); | ||||||
| 		if (currentCount >= (await this.roleService.getUserPolicies(me.id)).noteDraftLimit) { | 		if (currentCount >= policies.noteDraftLimit) { | ||||||
| 			throw new IdentifiableError('9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', 'Too many drafts'); | 			throw new IdentifiableError('9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', 'Too many drafts'); | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		if (data.isActuallyScheduled) { | ||||||
|  | 			const currentScheduledCount = await this.noteDraftsRepository.countBy({ | ||||||
|  | 				userId: me.id, | ||||||
|  | 				isActuallyScheduled: true, | ||||||
|  | 			}); | ||||||
|  | 			if (currentScheduledCount >= policies.scheduledNoteLimit) { | ||||||
|  | 				throw new IdentifiableError('c3275f19-4558-4c59-83e1-4f684b5fab66', 'Too many scheduled notes'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		//#endregion
 | 		//#endregion
 | ||||||
| 
 | 
 | ||||||
| 		await this.validate(me, data); | 		await this.validate(me, data); | ||||||
|  | @ -95,6 +105,20 @@ export class NoteDraftService { | ||||||
| 			throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); | 			throw new IdentifiableError('49cd6b9d-848e-41ee-b0b9-adaca711a6b1', 'No such note draft'); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		//#region check draft limit
 | ||||||
|  | 		const policies = await this.roleService.getUserPolicies(me.id); | ||||||
|  | 
 | ||||||
|  | 		if (!draft.isActuallyScheduled && data.isActuallyScheduled) { | ||||||
|  | 			const currentScheduledCount = await this.noteDraftsRepository.countBy({ | ||||||
|  | 				userId: me.id, | ||||||
|  | 				isActuallyScheduled: true, | ||||||
|  | 			}); | ||||||
|  | 			if (currentScheduledCount >= policies.scheduledNoteLimit) { | ||||||
|  | 				throw new IdentifiableError('bacdf856-5c51-4159-b88a-804fa5103be5', 'Too many scheduled notes'); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		//#endregion
 | ||||||
|  | 
 | ||||||
| 		await this.validate(me, data); | 		await this.validate(me, data); | ||||||
| 
 | 
 | ||||||
| 		const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update() | 		const updatedDraft = await this.noteDraftsRepository.createQueryBuilder().update() | ||||||
|  |  | ||||||
|  | @ -69,6 +69,7 @@ export type RolePolicies = { | ||||||
| 	chatAvailability: 'available' | 'readonly' | 'unavailable'; | 	chatAvailability: 'available' | 'readonly' | 'unavailable'; | ||||||
| 	uploadableFileTypes: string[]; | 	uploadableFileTypes: string[]; | ||||||
| 	noteDraftLimit: number; | 	noteDraftLimit: number; | ||||||
|  | 	scheduledNoteLimit: number; | ||||||
| 	watermarkAvailable: boolean; | 	watermarkAvailable: boolean; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -116,6 +117,7 @@ export const DEFAULT_POLICIES: RolePolicies = { | ||||||
| 		'audio/*', | 		'audio/*', | ||||||
| 	], | 	], | ||||||
| 	noteDraftLimit: 10, | 	noteDraftLimit: 10, | ||||||
|  | 	scheduledNoteLimit: 1, | ||||||
| 	watermarkAvailable: true, | 	watermarkAvailable: true, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -440,6 +442,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { | ||||||
| 				return [...set]; | 				return [...set]; | ||||||
| 			}), | 			}), | ||||||
| 			noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)), | 			noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)), | ||||||
|  | 			scheduledNoteLimit: calc('scheduledNoteLimit', vs => Math.max(...vs)), | ||||||
| 			watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)), | 			watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)), | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -129,6 +129,12 @@ export class NoteDraftEntityService implements OnModuleInit { | ||||||
| 				allowRenoteToExternal: channel.allowRenoteToExternal, | 				allowRenoteToExternal: channel.allowRenoteToExternal, | ||||||
| 				userId: channel.userId, | 				userId: channel.userId, | ||||||
| 			} : undefined, | 			} : undefined, | ||||||
|  | 			poll: noteDraft.hasPoll ? { | ||||||
|  | 				choices: noteDraft.pollChoices, | ||||||
|  | 				multiple: noteDraft.pollMultiple, | ||||||
|  | 				expiresAt: noteDraft.pollExpiresAt?.toISOString(), | ||||||
|  | 				expiredAfter: noteDraft.pollExpiredAfter, | ||||||
|  | 			} : null, | ||||||
| 
 | 
 | ||||||
| 			...(opts.detail ? { | 			...(opts.detail ? { | ||||||
| 				reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, { | 				reply: noteDraft.replyId ? nullIfEntityNotFound(this.noteEntityService.pack(noteDraft.replyId, me, { | ||||||
|  | @ -140,13 +146,6 @@ export class NoteDraftEntityService implements OnModuleInit { | ||||||
| 					detail: true, | 					detail: true, | ||||||
| 					skipHide: opts.skipHide, | 					skipHide: opts.skipHide, | ||||||
| 				})) : undefined, | 				})) : undefined, | ||||||
| 
 |  | ||||||
| 				poll: noteDraft.hasPoll ? { |  | ||||||
| 					choices: noteDraft.pollChoices, |  | ||||||
| 					multiple: noteDraft.pollMultiple, |  | ||||||
| 					expiresAt: noteDraft.pollExpiresAt?.toISOString(), |  | ||||||
| 					expiredAfter: noteDraft.pollExpiredAfter, |  | ||||||
| 				} : undefined, |  | ||||||
| 			} : {} ), | 			} : {} ), | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		cw: { | 		cw: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: true, nullable: true, | 			optional: false, nullable: true, | ||||||
| 		}, | 		}, | ||||||
| 		userId: { | 		userId: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
|  | @ -37,27 +37,23 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		replyId: { | 		replyId: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: true, nullable: true, | 			optional: false, nullable: true, | ||||||
| 			format: 'id', | 			format: 'id', | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, | 		}, | ||||||
| 		renoteId: { | 		renoteId: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: true, nullable: true, | 			optional: false, nullable: true, | ||||||
| 			format: 'id', | 			format: 'id', | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, | 		}, | ||||||
| 		reply: { | 		reply: { | ||||||
| 			type: 'object', | 			type: 'object', | ||||||
| 			optional: true, nullable: true, | 			optional: true, nullable: true, | ||||||
| 			ref: 'Note', | 			ref: 'Note', | ||||||
| 			description: 'The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null.', |  | ||||||
| 		}, | 		}, | ||||||
| 		renote: { | 		renote: { | ||||||
| 			type: 'object', | 			type: 'object', | ||||||
| 			optional: true, nullable: true, | 			optional: true, nullable: true, | ||||||
| 			ref: 'Note', | 			ref: 'Note', | ||||||
| 			description: 'The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null.', |  | ||||||
| 		}, | 		}, | ||||||
| 		visibility: { | 		visibility: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
|  | @ -66,7 +62,7 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		visibleUserIds: { | 		visibleUserIds: { | ||||||
| 			type: 'array', | 			type: 'array', | ||||||
| 			optional: true, nullable: false, | 			optional: false, nullable: false, | ||||||
| 			items: { | 			items: { | ||||||
| 				type: 'string', | 				type: 'string', | ||||||
| 				optional: false, nullable: false, | 				optional: false, nullable: false, | ||||||
|  | @ -75,7 +71,7 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		fileIds: { | 		fileIds: { | ||||||
| 			type: 'array', | 			type: 'array', | ||||||
| 			optional: true, nullable: false, | 			optional: false, nullable: false, | ||||||
| 			items: { | 			items: { | ||||||
| 				type: 'string', | 				type: 'string', | ||||||
| 				optional: false, nullable: false, | 				optional: false, nullable: false, | ||||||
|  | @ -93,11 +89,11 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		hashtag: { | 		hashtag: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: true, nullable: false, | 			optional: false, nullable: true, | ||||||
| 		}, | 		}, | ||||||
| 		poll: { | 		poll: { | ||||||
| 			type: 'object', | 			type: 'object', | ||||||
| 			optional: true, nullable: true, | 			optional: false, nullable: true, | ||||||
| 			properties: { | 			properties: { | ||||||
| 				expiresAt: { | 				expiresAt: { | ||||||
| 					type: 'string', | 					type: 'string', | ||||||
|  | @ -124,9 +120,8 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		channelId: { | 		channelId: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: true, nullable: true, | 			optional: false, nullable: true, | ||||||
| 			format: 'id', | 			format: 'id', | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, | 		}, | ||||||
| 		channel: { | 		channel: { | ||||||
| 			type: 'object', | 			type: 'object', | ||||||
|  | @ -160,7 +155,7 @@ export const packedNoteDraftSchema = { | ||||||
| 		}, | 		}, | ||||||
| 		localOnly: { | 		localOnly: { | ||||||
| 			type: 'boolean', | 			type: 'boolean', | ||||||
| 			optional: true, nullable: false, | 			optional: false, nullable: false, | ||||||
| 		}, | 		}, | ||||||
| 		reactionAcceptance: { | 		reactionAcceptance: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
|  |  | ||||||
|  | @ -317,6 +317,10 @@ export const packedRolePoliciesSchema = { | ||||||
| 			type: 'integer', | 			type: 'integer', | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
| 		}, | 		}, | ||||||
|  | 		scheduledNoteLimit: { | ||||||
|  | 			type: 'integer', | ||||||
|  | 			optional: false, nullable: false, | ||||||
|  | 		}, | ||||||
| 		watermarkAvailable: { | 		watermarkAvailable: { | ||||||
| 			type: 'boolean', | 			type: 'boolean', | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
|  |  | ||||||
|  | @ -40,11 +40,11 @@ export class PostScheduledNoteProcessorService { | ||||||
| 			const note = await this.noteCreateService.fetchAndCreate(draft.user, { | 			const note = await this.noteCreateService.fetchAndCreate(draft.user, { | ||||||
| 				createdAt: new Date(), | 				createdAt: new Date(), | ||||||
| 				fileIds: draft.fileIds, | 				fileIds: draft.fileIds, | ||||||
| 				poll: ps.poll ? { | 				poll: draft.hasPoll ? { | ||||||
| 					choices: ps.poll.choices, | 					choices: draft.pollChoices, | ||||||
| 					multiple: ps.poll.multiple ?? false, | 					multiple: draft.pollMultiple, | ||||||
| 					expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, | 					expiresAt: draft.pollExpiredAfter ? new Date(Date.now() + draft.pollExpiredAfter) : draft.pollExpiresAt ? new Date(draft.pollExpiresAt) : null, | ||||||
| 				} : undefined, | 				} : null, | ||||||
| 				text: draft.text ?? null, | 				text: draft.text ?? null, | ||||||
| 				replyId: draft.replyId, | 				replyId: draft.replyId, | ||||||
| 				renoteId: draft.renoteId, | 				renoteId: draft.renoteId, | ||||||
|  | @ -54,9 +54,6 @@ export class PostScheduledNoteProcessorService { | ||||||
| 				visibility: draft.visibility, | 				visibility: draft.visibility, | ||||||
| 				visibleUserIds: draft.visibleUserIds, | 				visibleUserIds: draft.visibleUserIds, | ||||||
| 				channelId: draft.channelId, | 				channelId: draft.channelId, | ||||||
| 				apMentions: draft.noExtractMentions ? [] : undefined, |  | ||||||
| 				apHashtags: draft.noExtractHashtags ? [] : undefined, |  | ||||||
| 				apEmojis: draft.noExtractEmojis ? [] : undefined, |  | ||||||
| 			}); | 			}); | ||||||
| 
 | 
 | ||||||
| 			// await不要
 | 			// await不要
 | ||||||
|  |  | ||||||
|  | @ -227,8 +227,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 					poll: ps.poll ? { | 					poll: ps.poll ? { | ||||||
| 						choices: ps.poll.choices, | 						choices: ps.poll.choices, | ||||||
| 						multiple: ps.poll.multiple ?? false, | 						multiple: ps.poll.multiple ?? false, | ||||||
| 						expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, | 						expiresAt: ps.poll.expiredAfter ? new Date(Date.now() + ps.poll.expiredAfter) : ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, | ||||||
| 					} : undefined, | 					} : null, | ||||||
| 					text: ps.text ?? null, | 					text: ps.text ?? null, | ||||||
| 					replyId: ps.replyId ?? null, | 					replyId: ps.replyId ?? null, | ||||||
| 					renoteId: ps.renoteId ?? null, | 					renoteId: ps.renoteId ?? null, | ||||||
|  | @ -246,16 +246,46 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				return { | 				return { | ||||||
| 					createdNote: await this.noteEntityService.pack(note, me), | 					createdNote: await this.noteEntityService.pack(note, me), | ||||||
| 				}; | 				}; | ||||||
| 			} catch (e) { | 			} catch (err) { | ||||||
| 				// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
 | 				// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
 | ||||||
| 				if (e instanceof IdentifiableError) { | 				if (err instanceof IdentifiableError) { | ||||||
| 					if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { | 					if (err.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { | ||||||
| 						throw new ApiError(meta.errors.containsProhibitedWords); | 						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); | 						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; | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -124,6 +124,12 @@ export const meta = { | ||||||
| 			id: '9ee33bbe-fde3-4c71-9b51-e50492c6b9c8', | 			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: { | 		cannotRenoteToExternal: { | ||||||
| 			message: 'Cannot Renote to External.', | 			message: 'Cannot Renote to External.', | ||||||
| 			code: 'CANNOT_RENOTE_TO_EXTERNAL', | 			code: 'CANNOT_RENOTE_TO_EXTERNAL', | ||||||
|  | @ -244,6 +250,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 							throw new ApiError(meta.errors.cannotReplyToInvisibleNote); | 							throw new ApiError(meta.errors.cannotReplyToInvisibleNote); | ||||||
| 						case '215dbc76-336c-4d2a-9605-95766ba7dab0': | 						case '215dbc76-336c-4d2a-9605-95766ba7dab0': | ||||||
| 							throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); | 							throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); | ||||||
|  | 						case 'c3275f19-4558-4c59-83e1-4f684b5fab66': | ||||||
|  | 							throw new ApiError(meta.errors.tooManyScheduledNotes); | ||||||
| 						default: | 						default: | ||||||
| 							throw err; | 							throw err; | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | @ -159,6 +159,12 @@ export const meta = { | ||||||
| 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', | 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY', | ||||||
| 			id: '215dbc76-336c-4d2a-9605-95766ba7dab0', | 			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: { | 	limit: { | ||||||
|  | @ -287,6 +293,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 							throw new ApiError(meta.errors.containsProhibitedWords); | 							throw new ApiError(meta.errors.containsProhibitedWords); | ||||||
| 						case '4de0363a-3046-481b-9b0f-feff3e211025': | 						case '4de0363a-3046-481b-9b0f-feff3e211025': | ||||||
| 							throw new ApiError(meta.errors.containsTooManyMentions); | 							throw new ApiError(meta.errors.containsTooManyMentions); | ||||||
|  | 						case 'bacdf856-5c51-4159-b88a-804fa5103be5': | ||||||
|  | 							throw new ApiError(meta.errors.tooManyScheduledNotes); | ||||||
| 						default: | 						default: | ||||||
| 							throw err; | 							throw err; | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | @ -614,13 +614,13 @@ function showOtherSettings() { | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			toggleReactionAcceptance(); | 			toggleReactionAcceptance(); | ||||||
| 		}, | 		}, | ||||||
| 	}, { | 	}, ...($i.policies.scheduledNoteLimit > 0 ? [{ | ||||||
| 		icon: 'ti ti-calendar-time', | 		icon: 'ti ti-calendar-time', | ||||||
| 		text: i18n.ts.schedulePost + '...', | 		text: i18n.ts.schedulePost + '...', | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			schedule(); | 			schedule(); | ||||||
| 		}, | 		}, | ||||||
| 	}, { type: 'divider' }, { | 	}] : []), { type: 'divider' }, { | ||||||
| 		type: 'switch', | 		type: 'switch', | ||||||
| 		icon: 'ti ti-eye', | 		icon: 'ti ti-eye', | ||||||
| 		text: i18n.ts.preview, | 		text: i18n.ts.preview, | ||||||
|  | @ -1181,8 +1181,10 @@ function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function showDraftMenu(ev: MouseEvent) { | function showDraftMenu(ev: MouseEvent) { | ||||||
| 	function showDraftsDialog() { | 	function showDraftsDialog(scheduled: boolean) { | ||||||
| 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {}, { | 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), { | ||||||
|  | 			scheduled, | ||||||
|  | 		}, { | ||||||
| 			restore: async (draft: Misskey.entities.NoteDraft) => { | 			restore: async (draft: Misskey.entities.NoteDraft) => { | ||||||
| 				text.value = draft.text ?? ''; | 				text.value = draft.text ?? ''; | ||||||
| 				useCw.value = draft.cw != null; | 				useCw.value = draft.cw != null; | ||||||
|  | @ -1254,14 +1256,14 @@ function showDraftMenu(ev: MouseEvent) { | ||||||
| 		text: i18n.ts._drafts.listDrafts, | 		text: i18n.ts._drafts.listDrafts, | ||||||
| 		icon: 'ti ti-cloud-download', | 		icon: 'ti ti-cloud-download', | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			showDraftsDialog(); | 			showDraftsDialog(false); | ||||||
| 		}, | 		}, | ||||||
| 	}, { type: 'divider' }, { | 	}, { type: 'divider' }, { | ||||||
| 		type: 'button', | 		type: 'button', | ||||||
| 		text: i18n.ts._drafts.listScheduledNotes, | 		text: i18n.ts._drafts.listScheduledNotes, | ||||||
| 		icon: 'ti ti-clock-down', | 		icon: 'ti ti-clock-down', | ||||||
| 		action: () => { | 		action: () => { | ||||||
| 			showDraftsDialog(); | 			showDraftsDialog(true); | ||||||
| 		}, | 		}, | ||||||
| 	}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); | 	}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints>( | ||||||
| 		} else if (err.code === 'ROLE_PERMISSION_DENIED') { | 		} else if (err.code === 'ROLE_PERMISSION_DENIED') { | ||||||
| 			title = i18n.ts.permissionDeniedError; | 			title = i18n.ts.permissionDeniedError; | ||||||
| 			text = i18n.ts.permissionDeniedErrorDescription; | 			text = i18n.ts.permissionDeniedErrorDescription; | ||||||
| 		} else if (err.code.startsWith('TOO_MANY')) { | 		} else if (err.code.startsWith('TOO_MANY')) { // TODO: バックエンドに kind: client/contentsLimitExceeded みたいな感じで送るように統一してもらってそれで判定する
 | ||||||
| 			title = i18n.ts.youCannotCreateAnymore; | 			title = i18n.ts.youCannotCreateAnymore; | ||||||
| 			text = `${i18n.ts.error}: ${err.id}`; | 			text = `${i18n.ts.error}: ${err.id}`; | ||||||
| 		} else if (err.message.startsWith('Unexpected token')) { | 		} else if (err.message.startsWith('Unexpected token')) { | ||||||
|  |  | ||||||
|  | @ -802,6 +802,25 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 				</div> | 				</div> | ||||||
| 			</MkFolder> | 			</MkFolder> | ||||||
| 
 | 
 | ||||||
|  | 			<MkFolder v-if="matchQuery([i18n.ts._role._options.scheduledNoteLimit, 'scheduledNoteLimit'])"> | ||||||
|  | 				<template #label>{{ i18n.ts._role._options.scheduledNoteLimit }}</template> | ||||||
|  | 				<template #suffix> | ||||||
|  | 					<span v-if="role.policies.scheduledNoteLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> | ||||||
|  | 					<span v-else>{{ role.policies.scheduledNoteLimit.value }}</span> | ||||||
|  | 					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.scheduledNoteLimit)"></i></span> | ||||||
|  | 				</template> | ||||||
|  | 				<div class="_gaps"> | ||||||
|  | 					<MkSwitch v-model="role.policies.scheduledNoteLimit.useDefault" :readonly="readonly"> | ||||||
|  | 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||||
|  | 					</MkSwitch> | ||||||
|  | 					<MkInput v-model="role.policies.scheduledNoteLimit.value" :disabled="role.policies.scheduledNoteLimit.useDefault" type="number" :readonly="readonly"> | ||||||
|  | 					</MkInput> | ||||||
|  | 					<MkRange v-model="role.policies.scheduledNoteLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||||
|  | 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||||
|  | 					</MkRange> | ||||||
|  | 				</div> | ||||||
|  | 			</MkFolder> | ||||||
|  | 
 | ||||||
| 			<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])"> | 			<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])"> | ||||||
| 				<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template> | 				<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template> | ||||||
| 				<template #suffix> | 				<template #suffix> | ||||||
|  | @ -831,6 +850,7 @@ import { watch, ref, computed } from 'vue'; | ||||||
| import { throttle } from 'throttle-debounce'; | import { throttle } from 'throttle-debounce'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
| import RolesEditorFormula from './RolesEditorFormula.vue'; | import RolesEditorFormula from './RolesEditorFormula.vue'; | ||||||
|  | import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue'; | ||||||
| import MkInput from '@/components/MkInput.vue'; | import MkInput from '@/components/MkInput.vue'; | ||||||
| import MkColorInput from '@/components/MkColorInput.vue'; | import MkColorInput from '@/components/MkColorInput.vue'; | ||||||
| import MkSelect from '@/components/MkSelect.vue'; | import MkSelect from '@/components/MkSelect.vue'; | ||||||
|  | @ -842,7 +862,6 @@ import FormSlot from '@/components/form/slot.vue'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { instance } from '@/instance.js'; | import { instance } from '@/instance.js'; | ||||||
| import { deepClone } from '@/utility/clone.js'; | import { deepClone } from '@/utility/clone.js'; | ||||||
| import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue'; |  | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'update:modelValue', v: any): void; | 	(ev: 'update:modelValue', v: any): void; | ||||||
|  |  | ||||||
|  | @ -304,6 +304,13 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 						</MkInput> | 						</MkInput> | ||||||
| 					</MkFolder> | 					</MkFolder> | ||||||
| 
 | 
 | ||||||
|  | 					<MkFolder v-if="matchQuery([i18n.ts._role._options.scheduledNoteLimit, 'scheduledNoteLimit'])"> | ||||||
|  | 						<template #label>{{ i18n.ts._role._options.scheduledNoteLimit }}</template> | ||||||
|  | 						<template #suffix>{{ policies.scheduledNoteLimit }}</template> | ||||||
|  | 						<MkInput v-model="policies.scheduledNoteLimit" type="number" :min="0"> | ||||||
|  | 						</MkInput> | ||||||
|  | 					</MkFolder> | ||||||
|  | 
 | ||||||
| 					<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])"> | 					<MkFolder v-if="matchQuery([i18n.ts._role._options.watermarkAvailable, 'watermarkAvailable'])"> | ||||||
| 						<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template> | 						<template #label>{{ i18n.ts._role._options.watermarkAvailable }}</template> | ||||||
| 						<template #suffix>{{ policies.watermarkAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | 						<template #suffix>{{ policies.watermarkAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | ||||||
|  |  | ||||||
|  | @ -3441,7 +3441,7 @@ type RoleLite = components['schemas']['RoleLite']; | ||||||
| type RolePolicies = components['schemas']['RolePolicies']; | type RolePolicies = components['schemas']['RolePolicies']; | ||||||
| 
 | 
 | ||||||
| // @public (undocumented) | // @public (undocumented) | ||||||
| export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "watermarkAvailable"]; | export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"]; | ||||||
| 
 | 
 | ||||||
| // @public (undocumented) | // @public (undocumented) | ||||||
| type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; | type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; | ||||||
|  |  | ||||||
|  | @ -4424,42 +4424,31 @@ export type components = { | ||||||
|             /** Format: date-time */ |             /** Format: date-time */ | ||||||
|             createdAt: string; |             createdAt: string; | ||||||
|             text: string | null; |             text: string | null; | ||||||
|             cw?: string | null; |             cw: string | null; | ||||||
|             /** Format: id */ |             /** Format: id */ | ||||||
|             userId: string; |             userId: string; | ||||||
|             user: components['schemas']['UserLite']; |             user: components['schemas']['UserLite']; | ||||||
|             /** |             /** Format: id */ | ||||||
|              * Format: id |             replyId: string | null; | ||||||
|              * @example xxxxxxxxxx |             /** Format: id */ | ||||||
|              */ |             renoteId: string | null; | ||||||
|             replyId?: string | null; |  | ||||||
|             /** |  | ||||||
|              * Format: id |  | ||||||
|              * @example xxxxxxxxxx |  | ||||||
|              */ |  | ||||||
|             renoteId?: string | null; |  | ||||||
|             /** @description The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null. */ |  | ||||||
|             reply?: components['schemas']['Note'] | null; |             reply?: components['schemas']['Note'] | null; | ||||||
|             /** @description The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null. */ |  | ||||||
|             renote?: components['schemas']['Note'] | null; |             renote?: components['schemas']['Note'] | null; | ||||||
|             /** @enum {string} */ |             /** @enum {string} */ | ||||||
|             visibility: 'public' | 'home' | 'followers' | 'specified'; |             visibility: 'public' | 'home' | 'followers' | 'specified'; | ||||||
|             visibleUserIds?: string[]; |             visibleUserIds: string[]; | ||||||
|             fileIds?: string[]; |             fileIds: string[]; | ||||||
|             files?: components['schemas']['DriveFile'][]; |             files?: components['schemas']['DriveFile'][]; | ||||||
|             hashtag?: string; |             hashtag: string | null; | ||||||
|             poll?: { |             poll: { | ||||||
|                 /** Format: date-time */ |                 /** Format: date-time */ | ||||||
|                 expiresAt?: string | null; |                 expiresAt?: string | null; | ||||||
|                 expiredAfter?: number | null; |                 expiredAfter?: number | null; | ||||||
|                 multiple: boolean; |                 multiple: boolean; | ||||||
|                 choices: string[]; |                 choices: string[]; | ||||||
|             } | null; |             } | null; | ||||||
|             /** |             /** Format: id */ | ||||||
|              * Format: id |             channelId: string | null; | ||||||
|              * @example xxxxxxxxxx |  | ||||||
|              */ |  | ||||||
|             channelId?: string | null; |  | ||||||
|             channel?: { |             channel?: { | ||||||
|                 id: string; |                 id: string; | ||||||
|                 name: string; |                 name: string; | ||||||
|  | @ -4468,7 +4457,7 @@ export type components = { | ||||||
|                 allowRenoteToExternal: boolean; |                 allowRenoteToExternal: boolean; | ||||||
|                 userId: string | null; |                 userId: string | null; | ||||||
|             } | null; |             } | null; | ||||||
|             localOnly?: boolean; |             localOnly: boolean; | ||||||
|             /** @enum {string|null} */ |             /** @enum {string|null} */ | ||||||
|             reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null; |             reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null; | ||||||
|             scheduledAt: number | null; |             scheduledAt: number | null; | ||||||
|  | @ -5289,6 +5278,7 @@ export type components = { | ||||||
|             /** @enum {string} */ |             /** @enum {string} */ | ||||||
|             chatAvailability: 'available' | 'readonly' | 'unavailable'; |             chatAvailability: 'available' | 'readonly' | 'unavailable'; | ||||||
|             noteDraftLimit: number; |             noteDraftLimit: number; | ||||||
|  |             scheduledNoteLimit: number; | ||||||
|             watermarkAvailable: boolean; |             watermarkAvailable: boolean; | ||||||
|         }; |         }; | ||||||
|         ReversiGameLite: { |         ReversiGameLite: { | ||||||
|  |  | ||||||
|  | @ -229,6 +229,7 @@ export const rolePolicies = [ | ||||||
| 	'chatAvailability', | 	'chatAvailability', | ||||||
| 	'uploadableFileTypes', | 	'uploadableFileTypes', | ||||||
| 	'noteDraftLimit', | 	'noteDraftLimit', | ||||||
|  | 	'scheduledNoteLimit', | ||||||
| 	'watermarkAvailable', | 	'watermarkAvailable', | ||||||
| ] as const; | ] as const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue