summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/notes/edit.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/endpoints/notes/edit.ts')
-rw-r--r--packages/backend/src/server/api/endpoints/notes/edit.ts125
1 files changed, 84 insertions, 41 deletions
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 590853e9cd..3caeda288b 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -11,6 +11,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { NoteEditService } from '@/core/NoteEditService.js';
import { DI } from '@/di-symbols.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApiError } from '../../error.js';
@@ -19,6 +20,8 @@ export const meta = {
requireCredential: true,
+ prohibitMoved: true,
+
limit: {
duration: ms('1hour'),
max: 300,
@@ -53,18 +56,42 @@ export const meta = {
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',
},
+ cannotReplyToInvisibleNote: {
+ message: 'You cannot reply to an invisible Note.',
+ code: 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE',
+ id: 'b98980fa-3780-406c-a935-b6d0eeee10d1',
+ },
+
cannotReplyToPureRenote: {
message: 'You can not reply to a pure Renote.',
code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
},
+ maxLength: {
+ message: 'You tried posting a note which is too long.',
+ code: 'MAX_LENGTH',
+ id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
+ },
+
+ cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
+ message: 'You cannot reply to a specified visibility note with extended visibility.',
+ code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
+ id: 'ed940410-535c-4d5e-bfa3-af798671e93c',
+ },
+
cannotCreateAlreadyExpiredPoll: {
message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@@ -83,6 +110,12 @@ export const meta = {
id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3',
},
+ noSuchFile: {
+ message: 'Some files are not found.',
+ code: 'NO_SUCH_FILE',
+ id: 'b6992544-63e7-67f0-fa7f-32444b1b5306',
+ },
+
accountLocked: {
message: 'You migrated. Your account is now locked.',
code: 'ACCOUNT_LOCKED',
@@ -137,17 +170,17 @@ export const meta = {
id: '33510210-8452-094c-6227-4a6c05d99f02',
},
- maxLength: {
- message: 'You tried posting a note which is too long.',
- code: 'MAX_LENGTH',
- id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
- },
-
containsProhibitedWords: {
message: 'Cannot post because it contains prohibited words.',
code: 'CONTAINS_PROHIBITED_WORDS',
id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
},
+
+ containsTooManyMentions: {
+ message: 'Cannot post because it exceeds the allowed number of mentions.',
+ code: 'CONTAINS_TOO_MANY_MENTIONS',
+ id: '4de0363a-3046-481b-9b0f-feff3e211025',
+ },
},
} as const;
@@ -201,7 +234,7 @@ export const paramDef = {
uniqueItems: true,
minItems: 2,
maxItems: 10,
- items: { type: 'string', minLength: 1, maxLength: 50 },
+ items: { type: 'string', minLength: 1, maxLength: 150 },
},
multiple: { type: 'boolean' },
expiresAt: { type: 'integer', nullable: true },
@@ -210,38 +243,33 @@ export const paramDef = {
required: ['choices'],
},
},
- anyOf: [
- {
- // (re)note with text, files and poll are optional
- properties: {
- text: {
- type: 'string',
- minLength: 1,
- nullable: false,
- },
+ // (re)note with text, files and poll are optional
+ if: {
+ properties: {
+ renoteId: {
+ type: 'null',
},
- 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 },
+ fileIds: {
+ type: 'null',
+ },
+ mediaIds: {
+ type: 'null',
+ },
+ poll: {
+ type: 'null',
},
- required: ['poll'],
},
- {
- // pure renote
- required: ['renoteId'],
+ },
+ then: {
+ properties: {
+ text: {
+ type: 'string',
+ minLength: 1,
+ pattern: '[^\\s]+',
+ },
},
- ],
+ required: ['text'],
+ },
} as const;
@Injectable()
@@ -292,7 +320,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.getMany();
if (files.length !== fileIds.length) {
- throw new ApiError(meta.errors.noSuchNote);
+ throw new ApiError(meta.errors.noSuchFile);
}
}
@@ -301,14 +329,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.renoteId === ps.editId) {
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
}
-
+
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 (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
+ } else if (isPureRenote(renote)) {
throw new ApiError(meta.errors.cannotReRenote);
}
@@ -329,6 +357,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
+ 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はせず必要な時に都度取得する
@@ -350,8 +386,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
- } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
+ } else if (isPureRenote(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
@@ -415,9 +455,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} catch (e) {
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
if (e instanceof IdentifiableError) {
- if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') throw new ApiError(meta.errors.containsProhibitedWords);
+ if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
+ throw new ApiError(meta.errors.containsProhibitedWords);
+ } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
+ throw new ApiError(meta.errors.containsTooManyMentions);
+ }
}
-
throw e;
}
});